Переглянути джерело

Merge branch 'master' into features/NetAnalyzers/CA1822

Giuseppe Lippolis 2 роки тому
батько
коміт
9802493285
100 змінених файлів з 1570 додано та 1145 видалено
  1. 1 0
      build/BuildTargets.targets
  2. 10 3
      native/Avalonia.Native/src/OSX/menu.h
  3. 20 6
      native/Avalonia.Native/src/OSX/menu.mm
  4. 1 0
      packages/Avalonia/AvaloniaBuildTasks.targets
  5. 3 3
      samples/BindingDemo/MainWindow.xaml
  6. 4 2
      samples/BindingDemo/TestItemView.xaml
  7. 9 1
      samples/ControlCatalog/App.xaml
  8. 96 62
      samples/ControlCatalog/App.xaml.cs
  9. 0 15
      samples/ControlCatalog/ControlCatalog.csproj
  10. 8 3
      samples/ControlCatalog/DecoratedWindow.xaml
  11. 5 3
      samples/ControlCatalog/MainView.xaml
  12. 2 37
      samples/ControlCatalog/MainView.xaml.cs
  13. 6 5
      samples/ControlCatalog/MainWindow.xaml
  14. 20 0
      samples/ControlCatalog/Models/StateData.cs
  15. 3 3
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  16. 1 0
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  17. 0 1
      samples/ControlCatalog/Pages/BorderPage.xaml
  18. 1 1
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
  19. 1 1
      samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
  20. 1 3
      samples/ControlCatalog/Pages/ColorPickerPage.xaml
  21. 5 3
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  22. 2 2
      samples/ControlCatalog/Pages/CompositionPage.axaml
  23. 4 2
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml
  24. 4 2
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  25. 4 2
      samples/ControlCatalog/Pages/CursorPage.xaml
  26. 1 1
      samples/ControlCatalog/Pages/DataGridPage.xaml
  27. 1 1
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  28. 3 1
      samples/ControlCatalog/Pages/ExpanderPage.xaml
  29. 5 3
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  30. 2 2
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  31. 3 1
      samples/ControlCatalog/Pages/LabelsPage.axaml
  32. 3 1
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  33. 5 3
      samples/ControlCatalog/Pages/MenuPage.xaml
  34. 3 1
      samples/ControlCatalog/Pages/NotificationsPage.xaml
  35. 7 4
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  36. 3 0
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  37. 1 1
      samples/ControlCatalog/Pages/OpenGlPage.xaml
  38. 3 1
      samples/ControlCatalog/Pages/PlatformInfoPage.xaml
  39. 1 1
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  40. 3 1
      samples/ControlCatalog/Pages/ScrollViewerPage.xaml
  41. 1 1
      samples/ControlCatalog/Pages/SliderPage.xaml
  42. 4 2
      samples/ControlCatalog/Pages/SplitViewPage.xaml
  43. 6 4
      samples/ControlCatalog/Pages/TabControlPage.xaml
  44. 10 32
      samples/ControlCatalog/Pages/TabControlPage.xaml.cs
  45. 6 4
      samples/ControlCatalog/Pages/TabStripPage.xaml
  46. 17 23
      samples/ControlCatalog/Pages/TabStripPage.xaml.cs
  47. 1 1
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  48. 1 2
      samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
  49. 3 1
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  50. 1 1
      samples/ControlCatalog/Pages/ViewboxPage.xaml
  51. 3 1
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  52. 10 10
      samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
  53. 22 22
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  54. 26 0
      samples/ControlCatalog/ViewModels/TabControlPageViewModel.cs
  55. 3 1
      samples/ControlCatalog/Views/CustomNotificationView.xaml
  56. 9 1
      samples/IntegrationTestApp/MainWindow.axaml
  57. 2 0
      samples/IntegrationTestApp/MainWindow.axaml.cs
  58. 2 1
      samples/IntegrationTestApp/ShowWindowTest.axaml
  59. 0 1
      samples/MobileSandbox/App.xaml
  60. 3 1
      samples/MobileSandbox/MainView.xaml
  61. 4 4
      samples/MobileSandbox/Views/CustomNotificationView.xaml
  62. 4 2
      samples/RenderDemo/MainWindow.xaml
  63. 3 1
      samples/RenderDemo/Pages/AnimationsPage.xaml
  64. 1 1
      samples/RenderDemo/Pages/CustomAnimatorPage.xaml
  65. 1 1
      samples/RenderDemo/Pages/GlyphRunPage.xaml
  66. 1 1
      samples/RenderDemo/Pages/LineBoundsPage.xaml
  67. 3 1
      samples/RenderDemo/Pages/Transform3DPage.axaml
  68. 2 0
      samples/RenderDemo/Pages/TransitionsPage.xaml
  69. 3 3
      samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
  70. 3 1
      samples/VirtualizationDemo/MainWindow.xaml
  71. 1 1
      src/Avalonia.Base/Animation/Animatable.cs
  72. 1 1
      src/Avalonia.Base/Animation/AnimationInstance`1.cs
  73. 9 0
      src/Avalonia.Base/Avalonia.Base.csproj
  74. 181 361
      src/Avalonia.Base/AvaloniaObject.cs
  75. 16 20
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  76. 9 6
      src/Avalonia.Base/AvaloniaProperty.cs
  77. 11 13
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
  78. 12 16
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs
  79. 32 0
      src/Avalonia.Base/Compatibility/CollectionCompatibilityExtensions.cs
  80. 0 20
      src/Avalonia.Base/Data/BindingNotification.cs
  81. 5 0
      src/Avalonia.Base/Data/BindingPriority.cs
  82. 66 64
      src/Avalonia.Base/Data/BindingValue.cs
  83. 5 4
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  84. 15 35
      src/Avalonia.Base/DirectPropertyBase.cs
  85. 7 0
      src/Avalonia.Base/IStyledPropertyAccessor.cs
  86. 1 7
      src/Avalonia.Base/Input/MouseDevice.cs
  87. 1 1
      src/Avalonia.Base/Input/PointerWheelEventArgs.cs
  88. 25 0
      src/Avalonia.Base/PropertyStore/AvaloniaPropertyDictionaryPool.cs
  89. 0 154
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  90. 148 0
      src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
  91. 0 82
      src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
  92. 76 0
      src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs
  93. 55 0
      src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs
  94. 168 0
      src/Avalonia.Base/PropertyStore/EffectiveValue.cs
  95. 270 0
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  96. 0 8
      src/Avalonia.Base/PropertyStore/IBatchUpdate.cs
  97. 0 18
      src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
  98. 0 28
      src/Avalonia.Base/PropertyStore/IValue.cs
  99. 30 0
      src/Avalonia.Base/PropertyStore/IValueEntry.cs
  100. 16 0
      src/Avalonia.Base/PropertyStore/IValueEntry`1.cs

+ 1 - 0
build/BuildTargets.targets

@@ -3,6 +3,7 @@
     <AvaloniaBuildTasksLocation>$(MSBuildThisFileDirectory)\..\src\Avalonia.Build.Tasks\bin\$(Configuration)\netstandard2.0\Avalonia.Build.Tasks.dll</AvaloniaBuildTasksLocation>
     <AvaloniaUseExternalMSBuild>true</AvaloniaUseExternalMSBuild>
     <AvaloniaXamlIlVerifyIl>true</AvaloniaXamlIlVerifyIl>
+    <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
   </PropertyGroup>
   <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.props"/>
   <Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.targets"/>

+ 10 - 3
native/Avalonia.Native/src/OSX/menu.h

@@ -59,11 +59,20 @@ public:
     void RaiseOnClicked();
 };
 
+class AvnAppMenu;
+
+@interface AvnMenuDelegate : NSObject<NSMenuDelegate>
+- (id) initWithParent: (AvnAppMenu*) parent;
+- (void) parentDestroyed;
+@end
+
+
 class AvnAppMenu : public ComSingleObject<IAvnMenu, &IID_IAvnMenu>
 {
 private:
     AvnMenu* _native;
     ComPtr<IAvnMenuEvents> _baseEvents;
+    AvnMenuDelegate* _delegate;
     
 public:
     FORWARD_IUNKNOWN()
@@ -83,12 +92,10 @@ public:
     virtual HRESULT SetTitle (char* utf8String) override;
     
     virtual HRESULT Clear () override;
+    virtual ~AvnAppMenu() override;
 };
 
 
-@interface AvnMenuDelegate : NSObject<NSMenuDelegate>
-- (id) initWithParent: (AvnAppMenu*) parent;
-@end
 
 #endif
 

+ 20 - 6
native/Avalonia.Native/src/OSX/menu.mm

@@ -292,8 +292,13 @@ void AvnAppMenuItem::RaiseOnClicked()
 AvnAppMenu::AvnAppMenu(IAvnMenuEvents* events)
 {
     _baseEvents = events;
-    id del = [[AvnMenuDelegate alloc] initWithParent: this];
-    _native = [[AvnMenu alloc] initWithDelegate: del];
+    _delegate = [[AvnMenuDelegate alloc] initWithParent: this];
+    _native = [[AvnMenu alloc] initWithDelegate: _delegate];
+}
+
+AvnAppMenu::~AvnAppMenu()
+{
+    [_delegate parentDestroyed];
 }
 
 
@@ -394,7 +399,7 @@ HRESULT AvnAppMenu::Clear()
 
 @implementation AvnMenuDelegate
 {
-    ComPtr<AvnAppMenu> _parent;
+    AvnAppMenu* _parent;
 }
 - (id) initWithParent:(AvnAppMenu *)parent
 {
@@ -402,6 +407,12 @@ HRESULT AvnAppMenu::Clear()
     _parent = parent;
     return self;
 }
+
+- (void) parentDestroyed
+{
+    _parent = nullptr;
+}
+
 - (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel
 {
     if(shouldCancel)
@@ -416,17 +427,20 @@ HRESULT AvnAppMenu::Clear()
 
 - (void)menuNeedsUpdate:(NSMenu *)menu
 {
-    _parent->RaiseNeedsUpdate();
+    if(_parent)
+        _parent->RaiseNeedsUpdate();
 }
 
 - (void)menuWillOpen:(NSMenu *)menu
 {
-    _parent->RaiseOpening();
+    if(_parent)
+        _parent->RaiseOpening();
 }
 
 - (void)menuDidClose:(NSMenu *)menu
 {
-    _parent->RaiseClosed();
+    if(_parent)
+        _parent->RaiseClosed();
 }
 
 @end

+ 1 - 0
packages/Avalonia/AvaloniaBuildTasks.targets

@@ -99,6 +99,7 @@
       AssemblyFile="@(IntermediateAssembly)"
       ReferencesFilePath="$(AvaloniaXamlReferencesTemporaryFilePath)"
       OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
+      RefAssemblyFile="@(IntermediateRefAssembly)"
       ProjectDirectory="$(MSBuildProjectDirectory)"
       VerifyIl="$(AvaloniaXamlIlVerifyIl)"
       ReportImportance="$(AvaloniaXamlReportImportance)"

+ 3 - 3
samples/BindingDemo/MainWindow.xaml

@@ -1,8 +1,8 @@
 <Window xmlns="https://github.com/avaloniaui"
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
         x:Class="BindingDemo.MainWindow"
-        xmlns:vm="clr-namespace:BindingDemo.ViewModels" 
-        xmlns:local="clr-namespace:BindingDemo"
+        xmlns:vm="using:BindingDemo.ViewModels" 
+        xmlns:local="using:BindingDemo"
         Title="AvaloniaUI Bindings Test"
         Width="800"
         Height="600"
@@ -81,7 +81,7 @@
           <TextBlock FontSize="16" Text="Multiple"/>
           <ListBox Items="{Binding Items}" SelectionMode="Multiple" Selection="{Binding Selection}"/>
         </StackPanel>
-        <ContentControl Content="{Binding SelectedItems[0]}">
+        <ContentControl Content="{ReflectionBinding Selection.SelectedItems[0]}">
           <ContentControl.DataTemplates>
             <DataTemplate DataType="vm:TestItem">
               <local:TestItemView></local:TestItemView>

+ 4 - 2
samples/BindingDemo/TestItemView.xaml

@@ -1,8 +1,10 @@
 <UserControl xmlns="https://github.com/avaloniaui"
         xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
-        x:Class="BindingDemo.TestItemView">
+        xmlns:viewModels="using:BindingDemo.ViewModels"
+        x:Class="BindingDemo.TestItemView"
+        x:DataType="viewModels:TestItem">
   <StackPanel>
     <TextBlock Classes="h1" Text="{Binding StringValue}"/>
     <TextBox Text="{Binding Detail}" AcceptsReturn="True"/>
   </StackPanel>
-</UserControl>
+</UserControl>

+ 9 - 1
samples/ControlCatalog/App.xaml

@@ -2,7 +2,6 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:vm="using:ControlCatalog.ViewModels"
              x:DataType="vm:ApplicationViewModel"
-             x:CompileBindings="True"
              Name="Avalonia ControlCatalog"
              x:Class="ControlCatalog.App">
   <Application.Resources>
@@ -10,6 +9,15 @@
       <ResourceDictionary.MergedDictionaries>
         <ResourceInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
       </ResourceDictionary.MergedDictionaries>
+
+      <StyleInclude x:Key="DataGridFluent" Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
+      <StyleInclude x:Key="DataGridSimple" Source="avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml" />
+      <StyleInclude x:Key="ColorPickerFluent" Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />
+      <StyleInclude x:Key="ColorPickerSimple" Source="avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml" />
+      <ResourceInclude x:Key="FluentAccentColors" Source="avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml" />
+      <ResourceInclude x:Key="FluentBaseLightColors" Source="avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml" />
+      <ResourceInclude x:Key="FluentBaseDarkColors" Source="avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml" />
+      <ResourceInclude x:Key="FluentBaseColors" Source="avares://Avalonia.Themes.Fluent/Accents/Base.xaml" />
     </ResourceDictionary>
   </Application.Resources>
   <Application.Styles>

+ 96 - 62
samples/ControlCatalog/App.xaml.cs

@@ -3,85 +3,46 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
-using Avalonia.Markup.Xaml.Styling;
 using Avalonia.Styling;
 using Avalonia.Themes.Simple;
 using Avalonia.Themes.Fluent;
+using ControlCatalog.Models;
 using ControlCatalog.ViewModels;
 
 namespace ControlCatalog
 {
     public class App : Application
     {
+        private readonly Styles _themeStylesContainer = new();
+        private FluentTheme? _fluentTheme;
+        private SimpleTheme? _simpleTheme;
+        private IResourceDictionary? _fluentBaseLightColors, _fluentBaseDarkColors;
+        private IStyle? _colorPickerFluent, _colorPickerSimple;
+        private IStyle? _dataGridFluent, _dataGridSimple;
+        
         public App()
         {
             DataContext = new ApplicationViewModel();
         }
 
-        public static readonly StyleInclude ColorPickerFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
-        {
-            Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml")
-        };
-
-        public static readonly StyleInclude ColorPickerSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
-        {
-            Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml")
-        };
-
-        public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
-        {
-            Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
-        };
-
-        public static readonly StyleInclude DataGridSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
-        {
-            Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml")
-        };
-
-        public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles"));
-
-        public static SimpleTheme Simple = new SimpleTheme(new Uri("avares://ControlCatalog/Styles"));
-
-        public static Styles SimpleLight = new Styles
-        {
-            new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
-            {
-                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
-            },
-            new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
-            {
-                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
-            },
-            new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
-            {
-                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml")
-            },
-            Simple
-        };
-
-        public static Styles SimpleDark = new Styles
-        {
-            new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
-            {
-                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
-            },
-            new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
-            {
-                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
-            },
-            new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
-            {
-                Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml")
-            },
-            Simple
-        };
-
         public override void Initialize()
         {
-            Styles.Insert(0, Fluent);
-            Styles.Insert(1, ColorPickerFluent);
-            Styles.Insert(2, DataGridFluent);
+            Styles.Add(_themeStylesContainer);
+
             AvaloniaXamlLoader.Load(this);
+
+            _fluentTheme = new FluentTheme();
+            _simpleTheme = new SimpleTheme();
+            _simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentAccentColors"]!);
+            _simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentBaseColors"]!);
+            _colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!;
+            _colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!;
+            _dataGridFluent = (IStyle)Resources["DataGridFluent"]!;
+            _dataGridSimple = (IStyle)Resources["DataGridSimple"]!;
+            _fluentBaseLightColors = (IResourceDictionary)Resources["FluentBaseLightColors"]!;
+            _fluentBaseDarkColors = (IResourceDictionary)Resources["FluentBaseDarkColors"]!;
+            
+            SetThemeVariant(CatalogTheme.FluentLight);
         }
 
         public override void OnFrameworkInitializationCompleted()
@@ -97,5 +58,78 @@ namespace ControlCatalog
 
             base.OnFrameworkInitializationCompleted();
         }
+
+        private CatalogTheme _prevTheme;
+        public static CatalogTheme CurrentTheme => ((App)Current!)._prevTheme; 
+        public static void SetThemeVariant(CatalogTheme theme)
+        {
+            var app = (App)Current!;
+            var prevTheme = app._prevTheme;
+            app._prevTheme = theme;
+            var shouldReopenWindow = theme switch
+            {
+                CatalogTheme.FluentLight => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
+                CatalogTheme.FluentDark => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
+                CatalogTheme.SimpleLight => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
+                CatalogTheme.SimpleDark => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
+                _ => throw new ArgumentOutOfRangeException(nameof(theme), theme, null)
+            };
+            
+            if (app._themeStylesContainer.Count == 0)
+            {
+                app._themeStylesContainer.Add(new Style());
+                app._themeStylesContainer.Add(new Style());
+                app._themeStylesContainer.Add(new Style());
+            }
+            
+            if (theme == CatalogTheme.FluentLight)
+            {
+                app._fluentTheme!.Mode = FluentThemeMode.Light;
+                app._themeStylesContainer[0] = app._fluentTheme;
+                app._themeStylesContainer[1] = app._colorPickerFluent!;
+                app._themeStylesContainer[2] = app._dataGridFluent!;
+            }
+            else if (theme == CatalogTheme.FluentDark)
+            {
+                app._fluentTheme!.Mode = FluentThemeMode.Dark;
+                app._themeStylesContainer[0] = app._fluentTheme;
+                app._themeStylesContainer[1] = app._colorPickerFluent!;
+                app._themeStylesContainer[2] = app._dataGridFluent!;
+            }
+            else if (theme == CatalogTheme.SimpleLight)
+            {
+                app._simpleTheme!.Mode = SimpleThemeMode.Light;
+                app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseDarkColors!);
+                app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseLightColors!);
+                app._themeStylesContainer[0] = app._simpleTheme;
+                app._themeStylesContainer[1] = app._colorPickerSimple!;
+                app._themeStylesContainer[2] = app._dataGridSimple!;
+            }
+            else if (theme == CatalogTheme.SimpleDark)
+            {
+                app._simpleTheme!.Mode = SimpleThemeMode.Dark;
+                app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseLightColors!);
+                app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseDarkColors!);
+                app._themeStylesContainer[0] = app._simpleTheme;
+                app._themeStylesContainer[1] = app._colorPickerSimple!;
+                app._themeStylesContainer[2] = app._dataGridSimple!;
+            }
+
+            if (shouldReopenWindow)
+            {
+                if (app.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
+                {
+                    var oldWindow = desktopLifetime.MainWindow;
+                    var newWindow = new MainWindow();
+                    desktopLifetime.MainWindow = newWindow;
+                    newWindow.Show();
+                    oldWindow?.Close();
+                }
+                else if (app.ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
+                {
+                    singleViewLifetime.MainView = new MainView();
+                }
+            }
+        }
     }
 }

+ 0 - 15
samples/ControlCatalog/ControlCatalog.csproj

@@ -14,9 +14,6 @@
     <AvaloniaResource Include="Assets\*" />
     <AvaloniaResource Include="Assets\Fonts\*" />
   </ItemGroup>
-  <ItemGroup>
-    <None Remove="Pages\NativeEmbedPage.xaml" />
-  </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" />
     <EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
@@ -35,17 +32,5 @@
     <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
   </ItemGroup>
 
-  <ItemGroup>
-    <AvaloniaResource Update="Pages\NativeEmbedPage.xaml">
-      <Generator>MSBuild:Compile</Generator>
-    </AvaloniaResource>
-  </ItemGroup>
-
-  <ItemGroup>
-    <Compile Update="Pages\NativeEmbedPage.xaml.cs">
-      <DependentUpon>%(Filename)</DependentUpon>
-    </Compile>
-  </ItemGroup>
-
   <Import Project="..\..\build\BuildTargets.targets" />
 </Project>

+ 8 - 3
samples/ControlCatalog/DecoratedWindow.xaml

@@ -2,7 +2,7 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         x:Class="ControlCatalog.DecoratedWindow"
         Title="Avalonia Control Gallery"
-        xmlns:local="clr-namespace:ControlCatalog" SystemDecorations="None" Name="Window">
+        SystemDecorations="None" Name="Window">
         <NativeMenu.Menu>
     <NativeMenu>
       <NativeMenuItem Header="Decorated">
@@ -43,8 +43,13 @@
                 <StackPanel>
                     <TextBlock>Hello world!</TextBlock>
 
-                    <CheckBox IsChecked="{Binding ElementName=Window, Path=HasSystemDecorations}">Decorated</CheckBox>
-
+                    <ComboBox SelectedItem="{Binding ElementName=Window, Path=SystemDecorations}">
+                      <ComboBox.Items>
+                        <SystemDecorations>None</SystemDecorations>
+                        <SystemDecorations>BorderOnly</SystemDecorations>
+                        <SystemDecorations>Full</SystemDecorations>
+                      </ComboBox.Items>
+                    </ComboBox>
                     <CheckBox IsChecked="{Binding ElementName=Window, Path=CanResize}">CanResize</CheckBox>
                 </StackPanel>
             </Border>

+ 5 - 3
samples/ControlCatalog/MainView.xaml

@@ -1,9 +1,11 @@
 <UserControl x:Class="ControlCatalog.MainView"
              xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:controls="clr-namespace:ControlSamples;assembly=ControlSamples"
-             xmlns:models="clr-namespace:ControlCatalog.Models"
-             xmlns:pages="clr-namespace:ControlCatalog.Pages">
+             xmlns:controls="using:ControlSamples"
+             xmlns:models="using:ControlCatalog.Models"
+             xmlns:pages="using:ControlCatalog.Pages"
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:DataType="viewModels:MainWindowViewModel">
   <Grid>
     <Grid.Styles>
       <Style Selector="TextBlock.h2">

+ 2 - 37
samples/ControlCatalog/MainView.xaml.cs

@@ -6,7 +6,6 @@ using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media;
 using Avalonia.Media.Immutable;
-using Avalonia.Themes.Fluent;
 using ControlCatalog.Models;
 using ControlCatalog.Pages;
 
@@ -31,46 +30,12 @@ namespace ControlCatalog
             }
 
             var themes = this.Get<ComboBox>("Themes");
+            themes.SelectedItem = App.CurrentTheme;
             themes.SelectionChanged += (sender, e) =>
             {
                 if (themes.SelectedItem is CatalogTheme theme)
                 {
-                    var themeStyle = Application.Current!.Styles[0];
-                    if (theme == CatalogTheme.FluentLight)
-                    {
-                        if (App.Fluent.Mode != FluentThemeMode.Light)
-                        {
-                            App.Fluent.Mode = FluentThemeMode.Light;
-                        }
-                        Application.Current.Styles[0] = App.Fluent;
-                        Application.Current.Styles[1] = App.ColorPickerFluent;
-                        Application.Current.Styles[2] = App.DataGridFluent;
-                    }
-                    else if (theme == CatalogTheme.FluentDark)
-                    {
-
-                        if (App.Fluent.Mode != FluentThemeMode.Dark)
-                        {
-                            App.Fluent.Mode = FluentThemeMode.Dark;
-                        }
-                        Application.Current.Styles[0] = App.Fluent;
-                        Application.Current.Styles[1] = App.ColorPickerFluent;
-                        Application.Current.Styles[2] = App.DataGridFluent;
-                    }
-                    else if (theme == CatalogTheme.SimpleLight)
-                    {
-                        App.Simple.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Light;
-                        Application.Current.Styles[0] = App.SimpleLight;
-                        Application.Current.Styles[1] = App.ColorPickerSimple;
-                        Application.Current.Styles[2] = App.DataGridSimple;
-                    }
-                    else if (theme == CatalogTheme.SimpleDark)
-                    {
-                        App.Simple.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Dark;
-                        Application.Current.Styles[0] = App.SimpleDark;
-                        Application.Current.Styles[1] = App.ColorPickerSimple;
-                        Application.Current.Styles[2] = App.DataGridSimple;
-                    }
+                    App.SetThemeVariant(theme);
                 }
             };
 

+ 6 - 5
samples/ControlCatalog/MainWindow.xaml

@@ -1,19 +1,20 @@
 <Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
         Width="1024" Height="800"
-        xmlns:pages="clr-namespace:ControlCatalog.Pages"
+        xmlns:pages="using:ControlCatalog.Pages"
         Title="Avalonia Control Gallery"
         Icon="/Assets/test_icon.ico"
-        xmlns:local="clr-namespace:ControlCatalog"
+        xmlns:local="using:ControlCatalog"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-        xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
-        xmlns:v="clr-namespace:ControlCatalog.Views"
+        xmlns:vm="clr-namespace:ControlCatalog.ViewModels;assembly=ControlCatalog"
+        xmlns:v="using:ControlCatalog.Views"
         ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}"
         ExtendClientAreaChromeHints="{Binding ChromeHints}"
         ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}"
         TransparencyLevelHint="{Binding TransparencyLevel}"        
         x:Name="MainWindow"
         Background="Transparent"
-        x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}">
+        x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}"
+        x:DataType="vm:MainWindowViewModel">
   <NativeMenu.Menu>
     <NativeMenu>
       <NativeMenuItem Header="File">

+ 20 - 0
samples/ControlCatalog/Models/StateData.cs

@@ -0,0 +1,20 @@
+namespace ControlCatalog.Models;
+
+public class StateData
+{
+    public string Name { get; private set; }
+    public string Abbreviation { get; private set; }
+    public string Capital { get; private set; }
+
+    public StateData(string name, string abbreviatoin, string capital)
+    {
+        Name = name;
+        Abbreviation = abbreviatoin;
+        Capital = capital;
+    }
+
+    public override string ToString()
+    {
+        return Name;
+    }
+}

+ 3 - 3
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@@ -2,8 +2,8 @@
              xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-             xmlns:sys="clr-namespace:System;assembly=netstandard"
+             xmlns:sys="using:System"
+             xmlns:models="using:ControlCatalog.Models"
              d:DesignHeight="600"
              d:DesignWidth="400">
   <StackPanel Orientation="Vertical"
@@ -45,7 +45,7 @@
 
       <StackPanel>
         <TextBlock Text="ValueMemberBinding" />
-        <AutoCompleteBox ValueMemberBinding="{Binding Capital}" />
+        <AutoCompleteBox ValueMemberBinding="{Binding Capital, x:DataType=models:StateData}" />
       </StackPanel>
       <StackPanel>
         <TextBlock Text="Multi-Binding" />

+ 1 - 0
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@@ -8,6 +8,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Avalonia.Data.Converters;
 using Avalonia.Data;
+using ControlCatalog.Models;
 
 namespace ControlCatalog.Pages
 {

+ 0 - 1
samples/ControlCatalog/Pages/BorderPage.xaml

@@ -1,7 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              d:DesignHeight="800"
              d:DesignWidth="400"
              x:Class="ControlCatalog.Pages.BorderPage">

+ 1 - 1
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml

@@ -1,7 +1,7 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              x:Class="ControlCatalog.Pages.ButtonSpinnerPage"
-             xmlns:sys="clr-namespace:System;assembly=netstandard">
+             xmlns:sys="using:System">
 
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h2">The ButtonSpinner control allows you to add button spinners to any element and then respond to the Spin event to manipulate that element.</TextBlock>

+ 1 - 1
samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml

@@ -1,6 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
+             xmlns:vm="using:ControlCatalog.ViewModels"
              x:DataType="vm:MainWindowViewModel"
              x:Class="ControlCatalog.Pages.CalendarDatePickerPage">
   <StackPanel Orientation="Vertical" Spacing="4">

+ 1 - 3
samples/ControlCatalog/Pages/ColorPickerPage.xaml

@@ -2,9 +2,7 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-             xmlns:controls="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls.ColorPicker"
-             xmlns:primitives="clr-namespace:Avalonia.Controls.Primitives;assembly=Avalonia.Controls"
-             xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker"
+             xmlns:controls="using:Avalonia.Controls"
              mc:Ignorable="d"
              d:DesignWidth="800"
              d:DesignHeight="450"

+ 5 - 3
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@@ -3,7 +3,9 @@
     xmlns="https://github.com/avaloniaui"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:col="using:System.Collections"
-    xmlns:sys="using:System">
+    xmlns:sys="using:System"
+    xmlns:viewModels="using:ControlCatalog.ViewModels"
+    x:DataType="viewModels:ComboBoxPageViewModel">
     <StackPanel Orientation="Vertical" Spacing="4">
         <TextBlock Classes="h2">A drop-down list.</TextBlock>
 
@@ -39,7 +41,7 @@
                         </col:ArrayList>
                     </ComboBox.Items>
                     <ComboBox.ItemTemplate>
-                        <DataTemplate>
+                        <DataTemplate x:DataType="sys:String">
                             <Panel>
                                 <TextBlock Text="{Binding}" />
                                 <TextBlock IsVisible="{Binding Converter={x:Static ObjectConverters.IsNull}}" Text="Null object" />
@@ -71,7 +73,7 @@
                     SelectedIndex="0"
                     WrapSelection="{Binding WrapSelection}">
                     <ComboBox.ItemTemplate>
-                        <DataTemplate>
+                        <DataTemplate x:DataType="FontFamily">
                             <TextBlock FontFamily="{Binding}" Text="{Binding Name}" />
                         </DataTemplate>
                     </ComboBox.ItemTemplate>

+ 2 - 2
samples/ControlCatalog/Pages/CompositionPage.axaml

@@ -1,6 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:pages="clr-namespace:ControlCatalog.Pages"
+             xmlns:pages="using:ControlCatalog.Pages"
              x:Class="ControlCatalog.Pages.CompositionPage">
     <StackPanel>
         <TextBlock Classes="h1">Implicit animations</TextBlock>
@@ -42,4 +42,4 @@
 
      
     </StackPanel>
-</UserControl>
+</UserControl>

+ 4 - 2
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml

@@ -3,9 +3,11 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
              d:DesignHeight="450"
              d:DesignWidth="800"
-             mc:Ignorable="d">
+             mc:Ignorable="d"
+             x:DataType="viewModels:ContextPageViewModel">
   <UserControl.Styles>
     <Style Selector="FlyoutPresenter.NoPadding">
       <Setter Property="Padding" Value="0" />
@@ -57,7 +59,7 @@
       </Border>
       <Border Classes="context-target">
         <Border.Styles>
-          <Style Selector="MenuFlyoutPresenter MenuItem">
+          <Style Selector="MenuFlyoutPresenter MenuItem" x:DataType="viewModels:MenuItemViewModel">
             <Setter Property="Header" Value="{Binding Header}"/>
             <Setter Property="Items" Value="{Binding Items}"/>
             <Setter Property="Command" Value="{Binding Command}"/>

+ 4 - 2
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@@ -1,6 +1,8 @@
 <UserControl x:Class="ControlCatalog.Pages.ContextMenuPage"
              xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:DataType="viewModels:ContextPageViewModel">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h2">A right click menu that can be applied to any control.</TextBlock>
 
@@ -47,7 +49,7 @@
       </Border>
       <Border>
         <Border.Styles>
-          <Style Selector="ContextMenu MenuItem">
+          <Style Selector="ContextMenu MenuItem" x:DataType="viewModels:MenuItemViewModel">
             <Setter Property="Header" Value="{Binding Header}"/>
             <Setter Property="Items" Value="{Binding Items}"/>
             <Setter Property="Command" Value="{Binding Command}"/>

+ 4 - 2
samples/ControlCatalog/Pages/CursorPage.xaml

@@ -1,6 +1,8 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.CursorPage">
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:Class="ControlCatalog.Pages.CursorPage"
+             x:DataType="viewModels:CursorPageViewModel">
   <Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*">
     <StackPanel Grid.ColumnSpan="2" Orientation="Vertical" Spacing="4">
       <TextBlock Classes="h2">Defines a cursor (mouse pointer)</TextBlock>
@@ -9,7 +11,7 @@
     <ListBox Grid.Row="1" Items="{Binding StandardCursors}" Margin="0 8 8 8">
       <ListBox.Styles>
         <Style Selector="ListBoxItem">
-          <Setter Property="Cursor" Value="{Binding Cursor}"/>
+          <Setter Property="Cursor" Value="{Binding Cursor}" x:DataType="viewModels:StandardCursorModel"/>
         </Style>
       </ListBox.Styles>
       <ListBox.ItemTemplate>

Різницю між файлами не показано, бо вона завелика
+ 1 - 1
samples/ControlCatalog/Pages/DataGridPage.xaml


+ 1 - 1
samples/ControlCatalog/Pages/DateTimePickerPage.xaml

@@ -2,7 +2,7 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-             xmlns:sys="clr-namespace:System;assembly=netstandard"
+             xmlns:sys="using:System"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:Class="ControlCatalog.Pages.DateTimePickerPage">
   <StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Stretch">

+ 3 - 1
samples/ControlCatalog/Pages/ExpanderPage.xaml

@@ -1,6 +1,8 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.ExpanderPage">
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:Class="ControlCatalog.Pages.ExpanderPage"
+             x:DataType="viewModels:ExpanderPageViewModel">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h2">Expands to show nested content</TextBlock>
 

+ 5 - 3
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@@ -1,6 +1,8 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.ItemsRepeaterPage">
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:Class="ControlCatalog.Pages.ItemsRepeaterPage"
+             x:DataType="viewModels:ItemsRepeaterPageViewModel">
   <UserControl.Styles>
     <Style Selector="ItemsRepeater TextBlock.oddTemplate">
       <Setter Property="Background" Value="Yellow" />
@@ -21,12 +23,12 @@
   </UserControl.Styles>
   <UserControl.Resources>
     <RecyclePool x:Key="RecyclePool" />
-    <DataTemplate x:Key="odd">
+    <DataTemplate x:Key="odd" x:DataType="viewModels:ItemsRepeaterPageViewModelItem">
       <TextBlock Classes="oddTemplate"
                  Height="{Binding Height}"
                  Text="{Binding Text}"/>
     </DataTemplate>
-    <DataTemplate x:Key="even">
+    <DataTemplate x:Key="even" x:DataType="viewModels:ItemsRepeaterPageViewModelItem">
       <TextBlock Classes="evenTemplate"
                  Height="{Binding Height}"
                  Text="{Binding Text}"/>

+ 2 - 2
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@@ -44,7 +44,7 @@ namespace ControlCatalog.Pages
 
         public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e)
         {
-            if (e.DataContext is ItemsRepeaterPageViewModel.Item item)
+            if (e.DataContext is ItemsRepeaterPageViewModelItem item)
             {
                 e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd";
             }
@@ -125,7 +125,7 @@ namespace ControlCatalog.Pages
 
         private void RepeaterClick(object? sender, PointerPressedEventArgs e)
         {
-            if ((e.Source as TextBlock)?.DataContext is ItemsRepeaterPageViewModel.Item item)
+            if ((e.Source as TextBlock)?.DataContext is ItemsRepeaterPageViewModelItem item)
             {
                 _viewModel.SelectedItem = item;
                 _selectedIndex = _viewModel.Items.IndexOf(item);

+ 3 - 1
samples/ControlCatalog/Pages/LabelsPage.axaml

@@ -2,9 +2,11 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:models="using:ControlCatalog.Models"
              mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="250"
              x:Class="ControlCatalog.Pages.LabelsPage"
-             x:Name="_labelsPage">
+             x:Name="_labelsPage"
+             x:DataType="models:Person">
     <UserControl.Styles>
         <Style Selector="Label">
             <Setter Property="VerticalAlignment" Value="Center"/>

+ 3 - 1
samples/ControlCatalog/Pages/ListBoxPage.xaml

@@ -1,6 +1,8 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.ListBoxPage">
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:Class="ControlCatalog.Pages.ListBoxPage"
+             x:DataType="viewModels:ListBoxPageViewModel">
   <DockPanel>
     <DockPanel.Styles>
       <Style Selector="ListBox ListBoxItem:nth-child(5n+3)">

+ 5 - 3
samples/ControlCatalog/Pages/MenuPage.xaml

@@ -1,6 +1,8 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.MenuPage">
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:Class="ControlCatalog.Pages.MenuPage"
+             x:DataType="viewModels:MenuPageViewModel">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h2">Exported menu fallback</TextBlock>
     <TextBlock>(Should be only visible on platforms without desktop-global menu bar)</TextBlock>
@@ -45,7 +47,7 @@
                 <TextBlock Classes="h3" Margin="4 8">Dyanamically generated</TextBlock>
                 <Menu Items="{Binding MenuItems}">
                     <Menu.Styles>
-                        <Style Selector="MenuItem">
+                        <Style Selector="MenuItem" x:DataType="viewModels:MenuItemViewModel">
                             <Setter Property="Header" Value="{Binding Header}"/>
                             <Setter Property="Items" Value="{Binding Items}"/>
                             <Setter Property="Command" Value="{Binding Command}"/>
@@ -68,7 +70,7 @@
                         <Separator/>
                         <MenuItem Header="_Recent" Items="{Binding RecentItems}">
                             <MenuItem.Styles>
-                                <Style Selector="MenuItem">
+                                <Style Selector="MenuItem" x:DataType="viewModels:MenuItemViewModel">
                                     <Setter Property="Header" Value="{Binding Header}"/>
                                 </Style>
                             </MenuItem.Styles>

+ 3 - 1
samples/ControlCatalog/Pages/NotificationsPage.xaml

@@ -1,6 +1,8 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.NotificationsPage">
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:Class="ControlCatalog.Pages.NotificationsPage"
+             x:DataType="viewModels:NotificationViewModel">
   <StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Left">
         <Button Content="Show Standard Managed Notification" Command="{Binding ShowManagedNotificationCommand}" />
         <Button Content="Show Custom Managed Notification" Command="{Binding ShowCustomManagedNotificationCommand}" />

+ 7 - 4
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@@ -1,8 +1,10 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:sys="clr-namespace:System;assembly=netstandard"
-             xmlns:converter="clr-namespace:ControlCatalog.Converter"
-             x:Class="ControlCatalog.Pages.NumericUpDownPage">
+             xmlns:sys="using:System"
+             xmlns:converter="using:ControlCatalog.Converter"
+             xmlns:pages="using:ControlCatalog.Pages"
+             x:Class="ControlCatalog.Pages.NumericUpDownPage"
+             x:DataType="pages:NumbersPageViewModel">
   <StackPanel Orientation="Vertical" Spacing="4"
               MaxWidth="800">
     <TextBlock Margin="2" Classes="h2" TextWrapping="Wrap">Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.</TextBlock>
@@ -43,7 +45,7 @@
                   VerticalAlignment="Center" Margin="2"/>
 
         <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">CultureInfo:</TextBlock>
-        <ComboBox Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}" SelectedItem="{Binding #upDown.CultureInfo}"
+        <ComboBox x:Name="CultureSelector" Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}"
                   VerticalAlignment="Center" Margin="2"/>
 
         <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock>
@@ -77,6 +79,7 @@
       <StackPanel Orientation="Vertical" Margin="10">
         <Label Target="upDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of decimal NumericUpDown:</Label>
         <NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5"
+                       NumberFormat="{Binding #CultureSelector.SelectedItem, Converter={x:Static pages:NumericUpDownPage.CultureConverter}}"
                        VerticalAlignment="Center" Value="{Binding DecimalValue}"
                        Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
       </StackPanel>

+ 3 - 0
samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using Avalonia.Controls;
+using Avalonia.Data.Converters;
 using Avalonia.Markup.Xaml;
 using MiniMvvm;
 
@@ -22,6 +23,8 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
 
+        public static IValueConverter CultureConverter =
+            new FuncValueConverter<CultureInfo, NumberFormatInfo>(c => (c ?? CultureInfo.CurrentCulture).NumberFormat);
     }
 
     public class NumbersPageViewModel : ViewModelBase

+ 1 - 1
samples/ControlCatalog/Pages/OpenGlPage.xaml

@@ -1,7 +1,7 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              x:Class="ControlCatalog.Pages.OpenGlPage"
-             xmlns:pages="clr-namespace:ControlCatalog.Pages">
+             xmlns:pages="using:ControlCatalog.Pages">
   <Grid>
     <pages:OpenGlPageControl x:Name="GL"/>
     <StackPanel>

+ 3 - 1
samples/ControlCatalog/Pages/PlatformInfoPage.xaml

@@ -3,9 +3,11 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
              d:DesignHeight="800"
              d:DesignWidth="400"
-             mc:Ignorable="d">
+             mc:Ignorable="d"
+             x:DataType="viewModels:PlatformInformationViewModel">
   <StackPanel Spacing="20">
     <TextBlock Text="{Binding PlatformInfo}" />
 

+ 1 - 1
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@@ -31,7 +31,7 @@
 
       <StackPanel Spacing="10">
         <ProgressBar VerticalAlignment="Center" IsIndeterminate="True" 
-                     Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.value}"/>
+                     Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}"/>
         <ProgressBar VerticalAlignment="Center" Value="5" Maximum="10" />
         <ProgressBar VerticalAlignment="Center" Value="50" />
         <ProgressBar VerticalAlignment="Center" Value="50" Minimum="25" Maximum="75" />

+ 3 - 1
samples/ControlCatalog/Pages/ScrollViewerPage.xaml

@@ -1,6 +1,8 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.ScrollViewerPage">
+             xmlns:pages="using:ControlCatalog.Pages"
+             x:Class="ControlCatalog.Pages.ScrollViewerPage"
+             x:DataType="pages:ScrollViewerPageViewModel">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h2">Allows for horizontal and vertical content scrolling.</TextBlock>
 

+ 1 - 1
samples/ControlCatalog/Pages/SliderPage.xaml

@@ -1,6 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:sys="clr-namespace:System;assembly=netstandard"
+             xmlns:sys="using:System"
              x:Class="ControlCatalog.Pages.SliderPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h2">A control that lets the user select from a range of values by moving a Thumb control along a Track.</TextBlock>

+ 4 - 2
samples/ControlCatalog/Pages/SplitViewPage.xaml

@@ -2,8 +2,10 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
-             x:Class="ControlCatalog.Pages.SplitViewPage">
+             x:Class="ControlCatalog.Pages.SplitViewPage"
+             x:DataType="viewModels:SplitViewPageViewModel">
 
   <Border>
 
@@ -51,7 +53,7 @@
         <!--{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}-->
         <SplitView Name="SplitView"
                       PanePlacement="{Binding PanePlacement}"
-                      PaneBackground="{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}"
+                      PaneBackground="{Binding ((Control)SelectedItem).Tag, ElementName=PaneBackgroundSelector, FallbackValue={x:Null}}"
                       OpenPaneLength="{Binding Value, ElementName=OpenPaneLengthSlider}"
                       CompactPaneLength="{Binding Value, ElementName=CompactPaneLengthSlider}"
                       DisplayMode="{Binding CurrentDisplayMode}">

+ 6 - 4
samples/ControlCatalog/Pages/TabControlPage.xaml

@@ -1,7 +1,9 @@
 <UserControl
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     x:Class="ControlCatalog.Pages.TabControlPage"
-    xmlns="https://github.com/avaloniaui">
+    xmlns="https://github.com/avaloniaui"
+    xmlns:viewModels="using:ControlCatalog.ViewModels"
+    x:DataType="viewModels:TabControlPageViewModel">
     <DockPanel>
         <TextBlock 
             DockPanel.Dock="Top" 
@@ -53,14 +55,14 @@
                     Margin="0 16"
                     TabStripPlacement="{Binding TabPlacement}">
                     <TabControl.ItemTemplate>
-                        <DataTemplate>
+                        <DataTemplate x:DataType="viewModels:TabControlPageViewModelItem">
                             <TextBlock
                                 Text="{Binding Header}">
                             </TextBlock>
                         </DataTemplate>
                     </TabControl.ItemTemplate>
                     <TabControl.ContentTemplate>
-                        <DataTemplate>
+                        <DataTemplate x:DataType="viewModels:TabControlPageViewModelItem">
                             <StackPanel Orientation="Vertical" Spacing="8">
                                 <TextBlock Text="{Binding Text}"/>
                                 <Image Source="{Binding Image}" Width="300"/>
@@ -68,7 +70,7 @@
                         </DataTemplate>
                     </TabControl.ContentTemplate>
                     <TabControl.Styles>
-                        <Style Selector="TabItem">
+                        <Style Selector="TabItem" x:DataType="viewModels:TabControlPageViewModelItem">
                             <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
                         </Style>
                     </TabControl.Styles>

+ 10 - 32
samples/ControlCatalog/Pages/TabControlPage.xaml.cs

@@ -5,8 +5,7 @@ using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media.Imaging;
 using Avalonia.Platform;
-
-using MiniMvvm;
+using ControlCatalog.ViewModels;
 
 namespace ControlCatalog.Pages
 {
@@ -14,33 +13,27 @@ namespace ControlCatalog.Pages
 
     public class TabControlPage : UserControl
     {
-        private static IBitmap LoadBitmap(string uri)
-        {
-            var assets = AvaloniaLocator.Current!.GetService<IAssetLoader>()!;
-            return new Bitmap(assets.Open(new Uri(uri)));
-        }
-
         public TabControlPage()
         {
             InitializeComponent();
 
-            DataContext = new PageViewModel
+            DataContext = new TabControlPageViewModel
             {
                 Tabs = new[]
                 {
-                    new TabItemViewModel
+                    new TabControlPageViewModelItem
                     {
                         Header = "Arch",
                         Text = "This is the first templated tab page.",
-                        Image = TabControlPage.LoadBitmap("avares://ControlCatalog/Assets/delicate-arch-896885_640.jpg"),
+                        Image = LoadBitmap("avares://ControlCatalog/Assets/delicate-arch-896885_640.jpg"),
                     },
-                    new TabItemViewModel
+                    new TabControlPageViewModelItem
                     {
                         Header = "Leaf",
                         Text = "This is the second templated tab page.",
-                        Image = TabControlPage.LoadBitmap("avares://ControlCatalog/Assets/maple-leaf-888807_640.jpg"),
+                        Image = LoadBitmap("avares://ControlCatalog/Assets/maple-leaf-888807_640.jpg"),
                     },
-                    new TabItemViewModel
+                    new TabControlPageViewModelItem
                     {
                         Header = "Disabled",
                         Text = "You should not see this.",
@@ -56,25 +49,10 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
 
-        private class PageViewModel : ViewModelBase
-        {
-            private Dock _tabPlacement;
-
-            public TabItemViewModel[]? Tabs { get; set; }
-
-            public Dock TabPlacement
-            {
-                get { return _tabPlacement; }
-                set { this.RaiseAndSetIfChanged(ref _tabPlacement, value); }
-            }
-        }
-
-        private class TabItemViewModel
+        private IBitmap LoadBitmap(string uri)
         {
-            public string? Header { get; set; }
-            public string? Text { get; set; }
-            public IBitmap? Image { get; set; }
-            public bool IsEnabled { get; set; } = true;
+            var assets = AvaloniaLocator.Current!.GetService<IAssetLoader>()!;
+            return new Bitmap(assets.Open(new Uri(uri)));
         }
     }
 }

+ 6 - 4
samples/ControlCatalog/Pages/TabStripPage.xaml

@@ -1,6 +1,8 @@
 <UserControl xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              x:Class="ControlCatalog.Pages.TabStripPage"
-             xmlns="https://github.com/avaloniaui">
+             xmlns="https://github.com/avaloniaui"
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:DataType="viewModels:TabControlPageViewModel">
     <StackPanel Orientation="Vertical" Spacing="4">
         <TextBlock Classes="h2">A control which displays a selectable strip of tabs</TextBlock>
 
@@ -16,14 +18,14 @@
         <Separator Margin="0 16"/>
 
         <TextBlock Classes="h1">Dynamically generated</TextBlock>
-        <TabStrip Items="{Binding}">
+        <TabStrip Items="{Binding Tabs}">
             <TabStrip.Styles>
-                <Style Selector="TabStripItem">
+                <Style Selector="TabStripItem" x:DataType="viewModels:TabControlPageViewModelItem">
                     <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
                 </Style>
             </TabStrip.Styles>
             <TabStrip.ItemTemplate>
-                <DataTemplate>
+                <DataTemplate x:DataType="viewModels:TabControlPageViewModelItem">
                     <TextBlock Text="{Binding Header}"/>
                 </DataTemplate>
             </TabStrip.ItemTemplate>

+ 17 - 23
samples/ControlCatalog/Pages/TabStripPage.xaml.cs

@@ -1,9 +1,6 @@
-using System;
-using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
-using Avalonia.Media.Imaging;
-using Avalonia.Platform;
+using ControlCatalog.ViewModels;
 
 namespace ControlCatalog.Pages
 {
@@ -13,21 +10,24 @@ namespace ControlCatalog.Pages
         {
             InitializeComponent();
 
-            DataContext = new[]
+            DataContext = new TabControlPageViewModel
             {
-                new TabStripItemViewModel
+                Tabs = new []
                 {
-                    Header = "Item 1",
-                },
-                new TabStripItemViewModel
-                {
-                    Header = "Item 2",
-                },
-                new TabStripItemViewModel
-                {
-                    Header = "Disabled",
-                    IsEnabled = false,
-                },
+                    new TabControlPageViewModelItem()
+                    {
+                        Header = "Item 1",
+                    },
+                    new TabControlPageViewModelItem
+                    {
+                        Header = "Item 2",
+                    },
+                    new TabControlPageViewModelItem
+                    {
+                        Header = "Disabled",
+                        IsEnabled = false,
+                    },
+                }
             };
         }
 
@@ -35,11 +35,5 @@ namespace ControlCatalog.Pages
         {
             AvaloniaXamlLoader.Load(this);
         }
-
-        private class TabStripItemViewModel
-        {
-            public string? Header { get; set; }
-            public bool IsEnabled { get; set; } = true;
-        }
     }
 }

+ 1 - 1
samples/ControlCatalog/Pages/TextBoxPage.xaml

@@ -1,7 +1,7 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              x:Class="ControlCatalog.Pages.TextBoxPage"
-             xmlns:sys="clr-namespace:System;assembly=netstandard">
+             xmlns:sys="using:System">
   <StackPanel Orientation="Vertical" Spacing="4">
     <Label Classes="h2">A control into which the user can input text</Label>
 

+ 1 - 2
samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml

@@ -3,11 +3,10 @@
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:vm="using:ControlCatalog.ViewModels"
-             xmlns:converter="clr-namespace:ControlCatalog.Converter"
+             xmlns:converter="using:ControlCatalog.Converter"
              xmlns:system="using:System"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:DataType="vm:TransitioningContentControlPageViewModel"
-             x:CompileBindings="True"
              x:Class="ControlCatalog.Pages.TransitioningContentControlPage">
 
     <UserControl.DataContext>

+ 3 - 1
samples/ControlCatalog/Pages/TreeViewPage.xaml

@@ -1,6 +1,8 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.TreeViewPage">
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:Class="ControlCatalog.Pages.TreeViewPage"
+             x:DataType="viewModels:TreeViewPageViewModel">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h2">Displays a hierachical tree of data.</TextBlock>
 

+ 1 - 1
samples/ControlCatalog/Pages/ViewboxPage.xaml

@@ -1,6 +1,6 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:collections="clr-namespace:System.Collections;assembly=netstandard"
+             xmlns:collections="using:System.Collections"
              x:Class="ControlCatalog.Pages.ViewboxPage">
 
     <Grid RowDefinitions="Auto,*,*">

+ 3 - 1
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml

@@ -2,8 +2,10 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
-             x:Class="ControlCatalog.Pages.WindowCustomizationsPage">
+             x:Class="ControlCatalog.Pages.WindowCustomizationsPage"
+             x:DataType="viewModels:MainWindowViewModel">
   <StackPanel Spacing="10"  Margin="25">
     <CheckBox Content="Extend Client Area to Decorations" IsChecked="{Binding ExtendClientAreaEnabled}" />
     <CheckBox Content="Title Bar" IsChecked="{Binding SystemTitleBarEnabled}" />    

+ 10 - 10
samples/ControlCatalog/ViewModels/CursorPageViewModel.cs

@@ -27,18 +27,18 @@ namespace ControlCatalog.ViewModels
         public IEnumerable<StandardCursorModel> StandardCursors { get; }
         
         public Cursor CustomCursor { get; }
-
-        public class StandardCursorModel
+    }
+    
+    public class StandardCursorModel
+    {
+        public StandardCursorModel(StandardCursorType type)
         {
-            public StandardCursorModel(StandardCursorType type)
-            {
-                Type = type;
-                Cursor = new Cursor(type);
-            }
+            Type = type;
+            Cursor = new Cursor(type);
+        }
 
-            public StandardCursorType Type { get; }
+        public StandardCursorType Type { get; }
             
-            public Cursor Cursor { get; }
-        }
+        public Cursor Cursor { get; }
     }
 }

+ 22 - 22
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@@ -10,25 +10,25 @@ namespace ControlCatalog.ViewModels
     {
         private int _newItemIndex = 1;
         private int _newGenerationIndex = 0;
-        private ObservableCollection<Item> _items;
+        private ObservableCollection<ItemsRepeaterPageViewModelItem> _items;
 
         public ItemsRepeaterPageViewModel()
         {
             _items = CreateItems();
         }
 
-        public ObservableCollection<Item> Items
+        public ObservableCollection<ItemsRepeaterPageViewModelItem> Items
         {
             get => _items;
             set => this.RaiseAndSetIfChanged(ref _items, value);
         }
 
-        public Item? SelectedItem { get; set; }
+        public ItemsRepeaterPageViewModelItem? SelectedItem { get; set; }
 
         public void AddItem()
         {
             var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1;
-            Items.Insert(index + 1, new Item(index + 1, $"New Item {_newItemIndex++}"));
+            Items.Insert(index + 1, new ItemsRepeaterPageViewModelItem(index + 1, $"New Item {_newItemIndex++}"));
         }
 
         public void RemoveItem()
@@ -59,33 +59,33 @@ namespace ControlCatalog.ViewModels
             Items = CreateItems();
         }
 
-        private ObservableCollection<Item> CreateItems()
+        private ObservableCollection<ItemsRepeaterPageViewModelItem> CreateItems()
         {
             var suffix = _newGenerationIndex == 0 ? string.Empty : $"[{_newGenerationIndex.ToString()}]";
 
             _newGenerationIndex++;
 
-            return new ObservableCollection<Item>(
-                Enumerable.Range(1, 100000).Select(i => new Item(i, $"Item {i.ToString()} {suffix}")));
+            return new ObservableCollection<ItemsRepeaterPageViewModelItem>(
+                Enumerable.Range(1, 100000).Select(i => new ItemsRepeaterPageViewModelItem(i, $"Item {i.ToString()} {suffix}")));
         }
+    }
+    
+    public class ItemsRepeaterPageViewModelItem : ViewModelBase
+    {
+        private double _height = double.NaN;
 
-        public class Item : ViewModelBase
+        public ItemsRepeaterPageViewModelItem(int index, string text)
         {
-            private double _height = double.NaN;
-
-            public Item(int index, string text)
-            {
-                Index = index;
-                Text = text;
-            }
-            public int Index { get; }
-            public string Text { get; }
+            Index = index;
+            Text = text;
+        }
+        public int Index { get; }
+        public string Text { get; }
             
-            public double Height 
-            {
-                get => _height;
-                set => this.RaiseAndSetIfChanged(ref _height, value);
-            }
+        public double Height 
+        {
+            get => _height;
+            set => this.RaiseAndSetIfChanged(ref _height, value);
         }
     }
 }

+ 26 - 0
samples/ControlCatalog/ViewModels/TabControlPageViewModel.cs

@@ -0,0 +1,26 @@
+using Avalonia.Controls;
+using Avalonia.Media.Imaging;
+using MiniMvvm;
+
+namespace ControlCatalog.ViewModels;
+
+public class TabControlPageViewModel : ViewModelBase
+{
+    private Dock _tabPlacement;
+
+    public TabControlPageViewModelItem[]? Tabs { get; set; }
+
+    public Dock TabPlacement
+    {
+        get { return _tabPlacement; }
+        set { this.RaiseAndSetIfChanged(ref _tabPlacement, value); }
+    }
+}
+
+public class TabControlPageViewModelItem
+{
+    public string? Header { get; set; }
+    public string? Text { get; set; }
+    public IBitmap? Image { get; set; }
+    public bool IsEnabled { get; set; } = true;
+}

+ 3 - 1
samples/ControlCatalog/Views/CustomNotificationView.xaml

@@ -1,6 +1,8 @@
 <UserControl xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Views.CustomNotificationView">
+             xmlns:viewModels="using:ControlCatalog.ViewModels"
+             x:Class="ControlCatalog.Views.CustomNotificationView"
+             x:DataType="viewModels:NotificationViewModel">
     <Border Padding="12" MinHeight="20" Background="DodgerBlue">
         <Grid ColumnDefinitions="Auto,*">
             <Panel Margin="0,0,12,0" Width="25" Height="25" VerticalAlignment="Top">

+ 9 - 1
samples/IntegrationTestApp/MainWindow.axaml

@@ -2,10 +2,12 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:integrationTestApp="using:IntegrationTestApp"
         mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
         x:Class="IntegrationTestApp.MainWindow"
         Name="MainWindow"
-        Title="IntegrationTestApp">
+        Title="IntegrationTestApp"
+        x:DataType="integrationTestApp:MainWindow">
   <NativeMenu.Menu>
     <NativeMenu>
       <NativeMenuItem Header="File">
@@ -126,6 +128,12 @@
             <ComboBoxItem>CenterScreen</ComboBoxItem>
             <ComboBoxItem>CenterOwner</ComboBoxItem>
           </ComboBox>
+          <ComboBox Name="ShowWindowState" SelectedIndex="0">
+            <ComboBoxItem>Normal</ComboBoxItem>
+            <ComboBoxItem>Minimized</ComboBoxItem>
+            <ComboBoxItem>Maximized</ComboBoxItem>
+            <ComboBoxItem>FullScreen</ComboBoxItem>
+          </ComboBox>
           <Button Name="ShowWindow">Show Window</Button>
           <Button Name="SendToBack">Send to Back</Button>
           <Button Name="ExitFullscreen">Exit Fullscreen</Button>

+ 2 - 0
samples/IntegrationTestApp/MainWindow.axaml.cs

@@ -59,6 +59,7 @@ namespace IntegrationTestApp
             var sizeTextBox = this.GetControl<TextBox>("ShowWindowSize");
             var modeComboBox = this.GetControl<ComboBox>("ShowWindowMode");
             var locationComboBox = this.GetControl<ComboBox>("ShowWindowLocation");
+            var stateComboBox = this.GetControl<ComboBox>("ShowWindowState");
             var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
             var owner = (Window)this.GetVisualRoot()!;
 
@@ -85,6 +86,7 @@ namespace IntegrationTestApp
             }
 
             sizeTextBox.Text = string.Empty;
+            window.WindowState = (WindowState)stateComboBox.SelectedIndex;
 
             switch (modeComboBox.SelectedIndex)
             {

+ 2 - 1
samples/IntegrationTestApp/ShowWindowTest.axaml

@@ -2,6 +2,7 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         x:Class="IntegrationTestApp.ShowWindowTest"
         Name="SecondaryWindow"
+        x:DataType="Window"
         Title="Show Window Test">
   <Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
     <Label Grid.Column="0" Grid.Row="1">Client Size</Label>
@@ -29,7 +30,7 @@
       <ComboBoxItem>Normal</ComboBoxItem>
       <ComboBoxItem>Minimized</ComboBoxItem>
       <ComboBoxItem>Maximized</ComboBoxItem>
-      <ComboBoxItem>Fullscreen</ComboBoxItem>
+      <ComboBoxItem>FullScreen</ComboBoxItem>
     </ComboBox>
 
     <Label Grid.Column="0" Grid.Row="8">Order (mac)</Label>

+ 0 - 1
samples/MobileSandbox/App.xaml

@@ -1,6 +1,5 @@
 <Application xmlns="https://github.com/avaloniaui"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:CompileBindings="True"
              Name="Mobile Sandbox"
              x:Class="MobileSandbox.App">
   <Application.Styles>

+ 3 - 1
samples/MobileSandbox/MainView.xaml

@@ -1,6 +1,8 @@
 <UserControl x:Class="MobileSandbox.MainView"
              xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mobileSandbox="using:MobileSandbox"
+             x:DataType="mobileSandbox:MainView">
   <StackPanel Margin="100 50" Spacing="50">
     <TextBlock Text="Login" Foreground="White" />
     <TextBox Watermark="Text" />

+ 4 - 4
samples/MobileSandbox/Views/CustomNotificationView.xaml

@@ -7,12 +7,12 @@
                 <TextBlock Text="&#xE115;" FontFamily="Segoe UI Symbol" FontSize="20" TextAlignment="Center" VerticalAlignment="Center"/>
             </Panel>
             <DockPanel Grid.Column="1">
-                <TextBlock DockPanel.Dock="Top" Text="{Binding Title}" FontWeight="Medium" />
+                <TextBlock DockPanel.Dock="Top" Text="{ReflectionBinding Title}" FontWeight="Medium" />
                 <StackPanel Spacing="20" DockPanel.Dock="Bottom" Margin="0,8,0,0" Orientation="Horizontal">
-                    <Button Content="No" DockPanel.Dock="Right" NotificationCard.CloseOnClick="True" Command="{Binding NoCommand}"  Margin="0,0,8,0" />
-                    <Button Content="Yes" DockPanel.Dock="Right" NotificationCard.CloseOnClick="True" Command="{Binding YesCommand}" />
+                    <Button Content="No" DockPanel.Dock="Right" NotificationCard.CloseOnClick="True" Command="{ReflectionBinding NoCommand}"  Margin="0,0,8,0" />
+                    <Button Content="Yes" DockPanel.Dock="Right" NotificationCard.CloseOnClick="True" Command="{ReflectionBinding YesCommand}" />
                 </StackPanel>
-                <TextBlock Text="{Binding Message}" TextWrapping="Wrap" Opacity=".8" Margin="0,8,0,0"/>
+                <TextBlock Text="{ReflectionBinding Message}" TextWrapping="Wrap" Opacity=".8" Margin="0,8,0,0"/>
             </DockPanel>
         </Grid>
     </Border>

+ 4 - 2
samples/RenderDemo/MainWindow.xaml

@@ -1,9 +1,11 @@
 <Window x:Class="RenderDemo.MainWindow"
         xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-        xmlns:controls="clr-namespace:ControlSamples;assembly=ControlSamples"
-        xmlns:pages="clr-namespace:RenderDemo.Pages"
+        xmlns:controls="using:ControlSamples"
+        xmlns:pages="using:RenderDemo.Pages"
+        xmlns:viewModels="using:RenderDemo.ViewModels"
         Title="AvaloniaUI Rendering Test"
+        x:DataType="viewModels:MainWindowViewModel"
         Width="{Binding Width, Mode=TwoWay}"
         Height="{Binding Height, Mode=TwoWay}">
   <controls:HamburgerMenu ExpandedModeThresholdWidth="760">

+ 3 - 1
samples/RenderDemo/Pages/AnimationsPage.xaml

@@ -1,7 +1,9 @@
 <UserControl 
   xmlns="https://github.com/avaloniaui" 
-  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
+  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+  xmlns:viewModels="using:RenderDemo.ViewModels"
   x:Class="RenderDemo.Pages.AnimationsPage"
+  x:DataType="viewModels:AnimationsPageViewModel"
   MaxWidth="600">
   <UserControl.Styles>
     <Styles>

+ 1 - 1
samples/RenderDemo/Pages/CustomAnimatorPage.xaml

@@ -1,7 +1,7 @@
 <UserControl 
   xmlns="https://github.com/avaloniaui" 
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
-  xmlns:pages="clr-namespace:RenderDemo.Pages"
+  xmlns:pages="using:RenderDemo.Pages"
   x:Class="RenderDemo.Pages.CustomAnimatorPage"
   MaxWidth="600">
   <Grid>

+ 1 - 1
samples/RenderDemo/Pages/GlyphRunPage.xaml

@@ -2,7 +2,7 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-             xmlns:local="clr-namespace:RenderDemo.Pages"
+             xmlns:local="using:RenderDemo.Pages"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:Class="RenderDemo.Pages.GlyphRunPage">
   <Grid

+ 1 - 1
samples/RenderDemo/Pages/LineBoundsPage.xaml

@@ -3,7 +3,7 @@
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
-             xmlns:controls="clr-namespace:RenderDemo.Controls"
+             xmlns:controls="using:RenderDemo.Controls"
              x:Class="RenderDemo.Pages.LineBoundsPage">
   <controls:LineBoundsDemoControl />
 </UserControl>

+ 3 - 1
samples/RenderDemo/Pages/Transform3DPage.axaml

@@ -2,8 +2,10 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:viewModels="using:RenderDemo.ViewModels"
              mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="700"
-             x:Class="RenderDemo.Pages.Transform3DPage">
+             x:Class="RenderDemo.Pages.Transform3DPage"
+             x:DataType="viewModels:Transform3DPageViewModel">
     <UserControl.Styles>
         <Styles>
             <Styles.Resources>

+ 2 - 0
samples/RenderDemo/Pages/TransitionsPage.xaml

@@ -1,7 +1,9 @@
 <UserControl 
   xmlns="https://github.com/avaloniaui" 
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+  xmlns:viewModels="using:RenderDemo.ViewModels"
   x:Class="RenderDemo.Pages.TransitionsPage"
+  x:DataType="viewModels:AnimationsPageViewModel"
   MaxWidth="600">
   <UserControl.Styles>
     <Styles>

+ 3 - 3
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@@ -194,7 +194,7 @@
                              VerticalAlignment="Center"
                              Classes="h1"
                              Margin="{StaticResource HeaderMarginExpandedPane}"
-                             Text="{Binding $parent[TabControl].SelectedItem.Header, FallbackValue=''}">
+                             Text="{Binding $parent[TabControl].SelectedItem.(TabItem.Header), FallbackValue=''}">
                     <TextBlock.Transitions>
                       <Transitions>
                         <ThicknessTransition Easing="{StaticResource SplitViewPaneAnimationEasing}"
@@ -250,10 +250,10 @@
       <Setter Property="PaneBackground" Value="{TemplateBinding PaneBackground}" />
     </Style>
     <Style Selector="^ /template/ SplitView[DisplayMode=Overlay]">
-      <Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" />
+      <Setter Property="Background" Value="{Binding $parent[catalog:HamburgerMenu].ContentBackground}" />
     </Style>
     <Style Selector="^ /template/ SplitView[DisplayMode=Inline] Border#BackgroundBorder">
-      <Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" />
+      <Setter Property="Background" Value="{Binding $parent[catalog:HamburgerMenu].ContentBackground}" />
       <Setter Property="BoxShadow" Value="{StaticResource NavigationContentShadow}" />
     </Style>
     <Style Selector="^ /template/ SplitView[DisplayMode=Inline][IsPaneOpen=True] Border#BackgroundBorder">

+ 3 - 1
samples/VirtualizationDemo/MainWindow.xaml

@@ -1,9 +1,11 @@
 <Window xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:viewModels="using:VirtualizationDemo.ViewModels"
         x:Class="VirtualizationDemo.MainWindow"
         Title="AvaloniaUI Virtualization Test"
         Width="800"
-        Height="600">
+        Height="600"
+        x:DataType="viewModels:MainWindowViewModel">
     <DockPanel LastChildFill="True" Margin="16">
         <StackPanel DockPanel.Dock="Right" 
                     Margin="16 0 0 0" 

+ 1 - 1
src/Avalonia.Base/Animation/Animatable.cs

@@ -235,7 +235,7 @@ namespace Avalonia.Animation
 
         private object? GetAnimationBaseValue(AvaloniaProperty property)
         {
-            var value = this.GetBaseValue(property, BindingPriority.LocalValue);
+            var value = this.GetBaseValue(property);
 
             if (value == AvaloniaProperty.UnsetValue)
             {

+ 1 - 1
src/Avalonia.Base/Animation/AnimationInstance`1.cs

@@ -229,7 +229,7 @@ namespace Avalonia.Animation
         private void UpdateNeutralValue()
         {
             var property = _animator.Property ?? throw new InvalidOperationException("Animator has no property specified.");
-            var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue);
+            var baseValue = _targetControl.GetBaseValue(property);
 
             _neutralValue = baseValue != AvaloniaProperty.UnsetValue ?
                 (T)baseValue! : (T)_targetControl.GetValue(property)!;

+ 9 - 0
src/Avalonia.Base/Avalonia.Base.csproj

@@ -28,6 +28,7 @@
 
   <ItemGroup Label="InternalsVisibleTo">
     <InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Controls, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Controls.ColorPicker, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Controls.DataGrid, PublicKey=$(AvaloniaPublicKey)" />
@@ -39,12 +40,20 @@
     <InternalsVisibleTo Include="Avalonia.Skia.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
+    <InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
     <InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
   </ItemGroup>
+  
+  <ItemGroup Label="Build dependency">
+    <ProjectReference Include="$(MSBuildThisFileDirectory)\..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj" 
+                      SetTargetFramework="TargetFramework=netstandard2.0"
+                      ReferenceOutputAssembly="false"
+                      SkipGetTargetFrameworkProperties="true" />
+  </ItemGroup>
 
   <ItemGroup>
     <Folder Include="Compatibility\" />

+ 181 - 361
src/Avalonia.Base/AvaloniaObject.cs

@@ -1,11 +1,11 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.Runtime.CompilerServices;
 using Avalonia.Data;
 using Avalonia.Diagnostics;
 using Avalonia.Logging;
 using Avalonia.PropertyStore;
-using Avalonia.Reactive;
 using Avalonia.Threading;
 
 namespace Avalonia
@@ -18,13 +18,11 @@ namespace Avalonia
     /// </remarks>
     public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
     {
+        private readonly ValueStore _values;
         private AvaloniaObject? _inheritanceParent;
-        private List<IDisposable>? _directBindings;
         private PropertyChangedEventHandler? _inpcChanged;
         private EventHandler<AvaloniaPropertyChangedEventArgs>? _propertyChanged;
         private List<AvaloniaObject>? _inheritanceChildren;
-        private ValueStore? _values;
-        private bool _batchUpdate;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="AvaloniaObject"/> class.
@@ -32,6 +30,7 @@ namespace Avalonia
         public AvaloniaObject()
         {
             VerifyAccess();
+            _values = new ValueStore(this);
         }
 
         /// <summary>
@@ -59,7 +58,7 @@ namespace Avalonia
         /// <value>
         /// The inheritance parent.
         /// </value>
-        protected AvaloniaObject? InheritanceParent
+        protected internal AvaloniaObject? InheritanceParent
         {
             get
             {
@@ -72,28 +71,10 @@ namespace Avalonia
 
                 if (_inheritanceParent != value)
                 {
-                    var oldParent = _inheritanceParent;
-                    var valuestore = _values;
-
                     _inheritanceParent?.RemoveInheritanceChild(this);
                     _inheritanceParent = value;
-
-                    var properties = AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType());
-                    var propertiesCount = properties.Count;
-
-                    for (var i = 0; i < propertiesCount; i++)
-                    {
-                        var property = properties[i];
-                        if (valuestore?.IsSet(property) == true)
-                        {
-                            // If local value set there can be no change.
-                            continue;
-                        }
-
-                        property.RouteInheritanceParentChanged(this, oldParent);
-                    }
-
                     _inheritanceParent?.AddInheritanceChild(this);
+                    _values.SetInheritanceParent(value);
                 }
             }
         }
@@ -118,24 +99,15 @@ namespace Avalonia
             set { this.Bind(binding.Property!, value); }
         }
 
-        private ValueStore Values
-        {
-            get
-            {
-                if (_values is null)
-                {
-                    _values = new ValueStore(this);
-
-                    if (_batchUpdate)
-                        _values.BeginBatchUpdate();
-                }
-
-                return _values;
-            }
-        }
-
+        /// <summary>
+        /// Returns a value indicating whether the current thread is the UI thread.
+        /// </summary>
+        /// <returns>true if the current thread is the UI thread; otherwise false.</returns>
         public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
 
+        /// <summary>
+        /// Checks that the current thread is the UI thread and throws if not.
+        /// </summary>
         public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
 
         /// <summary>
@@ -144,9 +116,9 @@ namespace Avalonia
         /// <param name="property">The property.</param>
         public void ClearValue(AvaloniaProperty property)
         {
-            property = property ?? throw new ArgumentNullException(nameof(property));
-
-            property.RouteClearValue(this);
+            _ = property ?? throw new ArgumentNullException(nameof(property));
+            VerifyAccess();
+            _values.ClearLocalValue(property);
         }
 
         /// <summary>
@@ -234,9 +206,12 @@ namespace Avalonia
         /// <returns>The value.</returns>
         public object? GetValue(AvaloniaProperty property)
         {
-            property = property ?? throw new ArgumentNullException(nameof(property));
+            _ = property ?? throw new ArgumentNullException(nameof(property));
 
-            return property.RouteGetValue(this);
+            if (property.IsDirect)
+                return property.RouteGetValue(this);
+            else
+                return _values.GetValue(property);
         }
 
         /// <summary>
@@ -247,10 +222,9 @@ namespace Avalonia
         /// <returns>The value.</returns>
         public T GetValue<T>(StyledPropertyBase<T> property)
         {
-            property = property ?? throw new ArgumentNullException(nameof(property));
+            _ = property ?? throw new ArgumentNullException(nameof(property));
             VerifyAccess();
-
-            return GetValueOrInheritedOrDefault(property);
+            return _values.GetValue(property);
         }
 
         /// <summary>
@@ -269,18 +243,11 @@ namespace Avalonia
         }
 
         /// <inheritdoc/>
-        public Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property, BindingPriority maxPriority)
+        public Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property)
         {
-            property = property ?? throw new ArgumentNullException(nameof(property));
+            _ = property ?? throw new ArgumentNullException(nameof(property));
             VerifyAccess();
-
-            if (_values is object &&
-                _values.TryGetValue(property, maxPriority, out var value))
-            {
-                return value;
-            }
-
-            return default;
+            return _values.GetBaseValue(property);
         }
 
         /// <summary>
@@ -346,26 +313,20 @@ namespace Avalonia
             T value,
             BindingPriority priority = BindingPriority.LocalValue)
         {
-            property = property ?? throw new ArgumentNullException(nameof(property));
+            _ = property ?? throw new ArgumentNullException(nameof(property));
             VerifyAccess();
+            ValidatePriority(priority);
 
-            LogPropertySet(property, value, priority);
+            LogPropertySet(property, value, BindingPriority.LocalValue);
 
             if (value is UnsetValueType)
             {
                 if (priority == BindingPriority.LocalValue)
-                {
-                    Values.ClearLocalValue(property);
-                }
-                else
-                {
-                    throw new NotSupportedException(
-                        "Cannot set property to Unset at non-local value priority.");
-                }
+                    _values.ClearLocalValue(property);
             }
-            else if (!(value is DoNothingType))
+            else if (value is not DoNothingType)
             {
-                return Values.SetValue(property, value, priority);
+                return _values.SetValue(property, value, priority);
             }
 
             return null;
@@ -382,6 +343,7 @@ namespace Avalonia
             property = property ?? throw new ArgumentNullException(nameof(property));
             VerifyAccess();
 
+            property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
             LogPropertySet(property, value, BindingPriority.LocalValue);
             SetDirectValueUnchecked(property, value);
         }
@@ -398,12 +360,52 @@ namespace Avalonia
         public IDisposable Bind(
             AvaloniaProperty property,
             IObservable<object?> source,
+            BindingPriority priority = BindingPriority.LocalValue) => property.RouteBind(this, source, priority);
+
+        /// <summary>
+        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="source">The observable.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>
+        /// A disposable which can be used to terminate the binding.
+        /// </returns>
+        public IDisposable Bind<T>(
+            StyledPropertyBase<T> property,
+            IObservable<object?> source,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            property = property ?? throw new ArgumentNullException(nameof(property));
+            source = source ?? throw new ArgumentNullException(nameof(source));
+            VerifyAccess();
+            ValidatePriority(priority);
+
+            return _values.AddBinding(property, source, priority);
+        }
+
+        /// <summary>
+        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="source">The observable.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>
+        /// A disposable which can be used to terminate the binding.
+        /// </returns>
+        public IDisposable Bind<T>(
+            StyledPropertyBase<T> property,
+            IObservable<T> source,
             BindingPriority priority = BindingPriority.LocalValue)
         {
             property = property ?? throw new ArgumentNullException(nameof(property));
             source = source ?? throw new ArgumentNullException(nameof(source));
+            VerifyAccess();
+            ValidatePriority(priority);
 
-            return property.RouteBind(this, source.ToBindingValue(), priority);
+            return _values.AddBinding(property, source, priority);
         }
 
         /// <summary>
@@ -424,8 +426,9 @@ namespace Avalonia
             property = property ?? throw new ArgumentNullException(nameof(property));
             source = source ?? throw new ArgumentNullException(nameof(source));
             VerifyAccess();
+            ValidatePriority(priority);
 
-            return Values.AddBinding(property, source, priority);
+            return _values.AddBinding(property, source, priority);
         }
 
         /// <summary>
@@ -439,10 +442,9 @@ namespace Avalonia
         /// </returns>
         public IDisposable Bind<T>(
             DirectPropertyBase<T> property,
-            IObservable<BindingValue<T>> source)
+            IObservable<object?> source)
         {
             property = property ?? throw new ArgumentNullException(nameof(property));
-            source = source ?? throw new ArgumentNullException(nameof(source));
             VerifyAccess();
 
             property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
@@ -452,48 +454,67 @@ namespace Avalonia
                 throw new ArgumentException($"The property {property.Name} is readonly.");
             }
 
-            Logger.TryGet(LogEventLevel.Verbose, LogArea.Property)?.Log(
-                this,
-                "Bound {Property} to {Binding} with priority LocalValue",
-                property,
-                GetDescription(source));
-
-            _directBindings ??= new List<IDisposable>();
-
-            return new DirectBindingSubscription<T>(this, property, source);
+            return _values.AddBinding(property, source);
         }
 
         /// <summary>
-        /// Coerces the specified <see cref="AvaloniaProperty"/>.
+        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
         /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
         /// <param name="property">The property.</param>
-        public void CoerceValue(AvaloniaProperty property)
+        /// <param name="source">The observable.</param>
+        /// <returns>
+        /// A disposable which can be used to terminate the binding.
+        /// </returns>
+        public IDisposable Bind<T>(
+            DirectPropertyBase<T> property,
+            IObservable<T> source)
         {
-            _values?.CoerceValue(property);
-        }
+            property = property ?? throw new ArgumentNullException(nameof(property));
+            VerifyAccess();
 
-        public void BeginBatchUpdate()
-        {
-            if (_batchUpdate)
+            property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
+
+            if (property.IsReadOnly)
             {
-                throw new InvalidOperationException("Batch update already in progress.");
+                throw new ArgumentException($"The property {property.Name} is readonly.");
             }
 
-            _batchUpdate = true;
-            _values?.BeginBatchUpdate();
+            return _values.AddBinding(property, source);
         }
 
-        public void EndBatchUpdate()
+        /// <summary>
+        /// Binds a <see cref="AvaloniaProperty"/> to an observable.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="source">The observable.</param>
+        /// <returns>
+        /// A disposable which can be used to terminate the binding.
+        /// </returns>
+        public IDisposable Bind<T>(
+            DirectPropertyBase<T> property,
+            IObservable<BindingValue<T>> source)
         {
-            if (!_batchUpdate)
+            property = property ?? throw new ArgumentNullException(nameof(property));
+            VerifyAccess();
+
+            property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
+
+            if (property.IsReadOnly)
             {
-                throw new InvalidOperationException("No batch update in progress.");
+                throw new ArgumentException($"The property {property.Name} is readonly.");
             }
 
-            _batchUpdate = false;
-            _values?.EndBatchUpdate();
+            return _values.AddBinding(property, source);
         }
 
+        /// <summary>
+        /// Coerces the specified <see cref="AvaloniaProperty"/>.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
+
         /// <inheritdoc/>
         internal void AddInheritanceChild(AvaloniaObject child)
         {
@@ -507,98 +528,12 @@ namespace Avalonia
             _inheritanceChildren?.Remove(child);
         }
 
-        internal void InheritedPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            Optional<T> newValue)
-        {
-            if (property.Inherits && (_values == null || !_values.IsSet(property)))
-            {
-                RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue);
-            }
-        }
-
         /// <inheritdoc/>
         Delegate[]? IAvaloniaObjectDebug.GetPropertyChangedSubscribers()
         {
             return _propertyChanged?.GetInvocationList();
         }
 
-        internal void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
-        {
-            var property = (StyledPropertyBase<T>)change.Property;
-
-            LogIfError(property, change.NewValue);
-
-            // If the change is to the effective value of the property and no old/new value is set
-            // then fill in the old/new value from property inheritance/default value. We don't do
-            // this for non-effective value changes because these are only needed for property
-            // transitions, where knowing e.g. that an inherited value is active at an arbitrary
-            // priority isn't of any use and would introduce overhead.
-            if (change.IsEffectiveValueChange && !change.OldValue.HasValue)
-            {
-                change.SetOldValue(GetInheritedOrDefault<T>(property));
-            }
-
-            if (change.IsEffectiveValueChange && !change.NewValue.HasValue)
-            {
-                change.SetNewValue(GetInheritedOrDefault(property));
-            }
-
-            if (!change.IsEffectiveValueChange ||
-                !EqualityComparer<T>.Default.Equals(change.OldValue.Value, change.NewValue.Value))
-            {
-                RaisePropertyChanged(change);
-
-                if (change.IsEffectiveValueChange)
-                {
-                    Logger.TryGet(LogEventLevel.Verbose, LogArea.Property)?.Log(
-                        this,
-                        "{Property} changed from {$Old} to {$Value} with priority {Priority}",
-                        property,
-                        change.OldValue,
-                        change.NewValue,
-                        change.Priority);
-                }
-            }
-        }
-
-        internal void Completed<T>(
-            StyledPropertyBase<T> property,
-            IPriorityValueEntry entry,
-            Optional<T> oldValue) 
-        {
-            var change = new AvaloniaPropertyChangedEventArgs<T>(
-                this,
-                property,
-                oldValue,
-                default,
-                BindingPriority.Unset);
-            ValueChanged(change);
-        }
-
-        /// <summary>
-        /// Called for each inherited property when the <see cref="InheritanceParent"/> changes.
-        /// </summary>
-        /// <typeparam name="T">The type of the property value.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="oldParent">The old inheritance parent.</param>
-        internal void InheritanceParentChanged<T>(
-            StyledPropertyBase<T> property,
-            AvaloniaObject? oldParent)
-        {
-            var oldValue = oldParent is not null ?
-                oldParent.GetValueOrInheritedOrDefault(property) :
-                property.GetDefaultValue(GetType());
-
-            var newValue = GetInheritedOrDefault(property);
-
-            if (!EqualityComparer<T>.Default.Equals(oldValue, newValue))
-            {
-                RaisePropertyChanged(property, oldValue, newValue);
-            }
-        }
-
         internal AvaloniaPropertyValue GetDiagnosticInternal(AvaloniaProperty property)
         {
             if (property.IsDirect)
@@ -626,19 +561,23 @@ namespace Avalonia
                 "Unset");
         }
 
+        internal ValueStore GetValueStore() => _values;
+        internal IReadOnlyList<AvaloniaObject>? GetInheritanceChildren() => _inheritanceChildren;
+
         /// <summary>
-        /// Logs a binding error for a property.
+        /// Gets a logger to which a binding warning may be written.
         /// </summary>
         /// <param name="property">The property that the error occurred on.</param>
-        /// <param name="e">The binding error.</param>
-        protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e)
+        /// <param name="e">The binding exception, if any.</param>
+        /// <remarks>
+        /// This is overridden in <see cref="Visual"/> to prevent logging binding errors when a
+        /// control is not attached to the visual tree.
+        /// </remarks>
+        internal virtual ParametrizedLogger? GetBindingWarningLogger(
+            AvaloniaProperty property,
+            Exception? e)
         {
-            Logger.TryGet(LogEventLevel.Warning, LogArea.Binding)?.Log(
-                this,
-                "Error in binding to {Target}.{Property}: {Message}",
-                this,
-                property,
-                e.Message);
+            return Logger.TryGet(LogEventLevel.Warning, LogArea.Binding);
         }
 
         /// <summary>
@@ -675,6 +614,22 @@ namespace Avalonia
         {
         }
 
+        /// <summary>
+        /// Raises the <see cref="PropertyChanged"/> event for a direct property.
+        /// </summary>
+        /// <param name="property">The property that has changed.</param>
+        /// <param name="oldValue">The old property value.</param>
+        /// <param name="newValue">The new property value.</param>
+        /// <param name="priority">The priority of the binding that produced the value.</param>
+        private protected void RaisePropertyChanged<T>(
+            DirectPropertyBase<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            RaisePropertyChanged(property, oldValue, newValue, priority, true);
+        }
+
         /// <summary>
         /// Raises the <see cref="PropertyChanged"/> event.
         /// </summary>
@@ -682,18 +637,32 @@ namespace Avalonia
         /// <param name="oldValue">The old property value.</param>
         /// <param name="newValue">The new property value.</param>
         /// <param name="priority">The priority of the binding that produced the value.</param>
-        protected internal void RaisePropertyChanged<T>(
+        /// <param name="isEffectiveValue">
+        /// Whether the notification represents a change to the effective value of the property.
+        /// </param>
+        internal void RaisePropertyChanged<T>(
             AvaloniaProperty<T> property,
             Optional<T> oldValue,
             BindingValue<T> newValue,
-            BindingPriority priority = BindingPriority.LocalValue)
+            BindingPriority priority,
+            bool isEffectiveValue)
         {
-            RaisePropertyChanged(new AvaloniaPropertyChangedEventArgs<T>(
+            var e = new AvaloniaPropertyChangedEventArgs<T>(
                 this,
                 property,
                 oldValue,
                 newValue,
-                priority));
+                priority,
+                isEffectiveValue);
+
+            OnPropertyChangedCore(e);
+
+            if (isEffectiveValue)
+            {
+                property.NotifyChanged(e);
+                _propertyChanged?.Invoke(this, e);
+                _inpcChanged?.Invoke(this, new PropertyChangedEventArgs(property.Name));
+            }
         }
 
         /// <summary>
@@ -718,110 +687,24 @@ namespace Avalonia
 
             var old = field;
             field = value;
-            RaisePropertyChanged(property, old, value);
+            RaisePropertyChanged(property, old, value, BindingPriority.LocalValue, true);
             return true;
         }
 
-        private T GetInheritedOrDefault<T>(StyledPropertyBase<T> property)
-        {
-            if (property.Inherits && InheritanceParent is AvaloniaObject o)
-            {
-                return o.GetValueOrInheritedOrDefault(property);
-            }
-
-            return property.GetDefaultValue(GetType());
-        }
-
-        private T GetValueOrInheritedOrDefault<T>(
-            StyledPropertyBase<T> property,
-            BindingPriority maxPriority = BindingPriority.Animation)
-        {
-            var o = this;
-            var inherits = property.Inherits;
-            var value = default(T);
-
-            while (o != null)
-            {
-                var values = o._values;
-
-                if (values != null
-                    && values.TryGetValue(property, maxPriority, out value) == true)
-                {
-                    return value;
-                }
-
-                if (!inherits)
-                {
-                    break;
-                }
-
-                o = o.InheritanceParent as AvaloniaObject;
-            }
-
-            return property.GetDefaultValue(GetType());
-        }
-
-        protected internal void RaisePropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
-        {
-            VerifyAccess();
-
-            if (change.IsEffectiveValueChange)
-            {
-                change.Property.Notifying?.Invoke(this, true);
-            }
-
-            try
-            {
-                OnPropertyChangedCore(change);
-
-                if (change.IsEffectiveValueChange)
-                {
-                    change.Property.NotifyChanged(change);
-                    _propertyChanged?.Invoke(this, change);
-
-                    if (_inpcChanged != null)
-                    {
-                        var inpce = new PropertyChangedEventArgs(change.Property.Name);
-                        _inpcChanged(this, inpce);
-                    }
-
-                    if (change.Property.Inherits && _inheritanceChildren != null)
-                    {
-                        foreach (var child in _inheritanceChildren)
-                        {
-                            child.InheritedPropertyChanged(
-                                change.Property,
-                                change.OldValue,
-                                change.NewValue.ToOptional());
-                        }
-                    }
-                }
-            }
-            finally
-            {
-                if (change.IsEffectiveValueChange)
-                {
-                    change.Property.Notifying?.Invoke(this, false);
-                }
-            }
-        }
-
         /// <summary>
         /// Sets the value of a direct property.
         /// </summary>
         /// <param name="property">The property.</param>
         /// <param name="value">The value.</param>
-        private void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, T value)
+        internal void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, T value)
         {
-            var p = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
-
             if (value is UnsetValueType)
             {
-                p.InvokeSetter(this, p.GetUnsetValue(GetType()));
+                property.InvokeSetter(this, property.GetUnsetValue(GetType()));
             }
             else if (!(value is DoNothingType))
             {
-                p.InvokeSetter(this, value);
+                property.InvokeSetter(this, value);
             }
         }
 
@@ -830,16 +713,9 @@ namespace Avalonia
         /// </summary>
         /// <param name="property">The property.</param>
         /// <param name="value">The value.</param>
-        private void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, BindingValue<T> value)
+        internal void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, BindingValue<T> value)
         {
-            var p = AvaloniaPropertyRegistry.Instance.FindRegisteredDirect(this, property);
-
-            if (p == null)
-            {
-                throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}");
-            }
-
-            LogIfError(property, value);
+            LoggingUtils.LogIfNecessary(this, property, value);
 
             switch (value.Type)
             {
@@ -858,7 +734,7 @@ namespace Avalonia
                     break;
             }
 
-            var metadata = p.GetMetadata(GetType());
+            var metadata = property.GetMetadata(GetType());
 
             if (metadata.EnableDataValidation == true)
             {
@@ -877,29 +753,6 @@ namespace Avalonia
             return description?.Description ?? o.ToString() ?? o.GetType().Name;
         }
 
-        /// <summary>
-        /// Logs a message if the notification represents a binding error.
-        /// </summary>
-        /// <param name="property">The property being bound.</param>
-        /// <param name="value">The binding notification.</param>
-        private void LogIfError<T>(AvaloniaProperty property, BindingValue<T> value)
-        {
-            if (value.HasError)
-            {
-                if (value.Error is AggregateException aggregate)
-                {
-                    foreach (var inner in aggregate.InnerExceptions)
-                    {
-                        LogBindingError(property, inner);
-                    }
-                }
-                else
-                {
-                    LogBindingError(property, value.Error!);
-                }
-            }
-        }
-
         /// <summary>
         /// Logs a property set message.
         /// </summary>
@@ -916,49 +769,16 @@ namespace Avalonia
                 priority);
         }
 
-        private class DirectBindingSubscription<T> : IObserver<BindingValue<T>>, IDisposable
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void ValidatePriority(BindingPriority priority)
         {
-            private readonly AvaloniaObject _owner;
-            private readonly DirectPropertyBase<T> _property;
-            private readonly IDisposable _subscription;
-
-            public DirectBindingSubscription(
-                AvaloniaObject owner,
-                DirectPropertyBase<T> property,
-                IObservable<BindingValue<T>> source)
-            {
-                _owner = owner;
-                _property = property;
-                _owner._directBindings!.Add(this);
-                _subscription = source.Subscribe(this);
-            }
-
-            public void Dispose()
-            {
-                // _subscription can be null, if Subscribe failed with an exception.
-                _subscription?.Dispose();
-                _owner._directBindings!.Remove(this);
-            }
-
-            public void OnCompleted() => Dispose();
-            public void OnError(Exception error) => Dispose();
-            public void OnNext(BindingValue<T> value)
-            {
-                if (Dispatcher.UIThread.CheckAccess())
-                {
-                    _owner.SetDirectValueUnchecked(_property, value);
-                }
-                else
-                {
-                    // To avoid allocating closure in the outer scope we need to capture variables
-                    // locally. This allows us to skip most of the allocations when on UI thread.
-                    var instance = _owner;
-                    var property = _property;
-                    var newValue = value;
+            if (priority < BindingPriority.Animation || priority >= BindingPriority.Inherited)
+                ThrowInvalidPriority(priority);
+        }
 
-                    Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue));
-                }
-            }
+        private static void ThrowInvalidPriority(BindingPriority priority)
+        {
+            throw new ArgumentException($"Invalid priority ${priority}", nameof(priority));
         }
     }
 }

+ 16 - 20
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -261,7 +261,6 @@ namespace Avalonia
             }
 
             throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
-
         }
 
         /// <summary>
@@ -280,14 +279,17 @@ namespace Avalonia
             IObservable<T> source,
             BindingPriority priority = BindingPriority.LocalValue)
         {
-            target = target ?? throw new ArgumentNullException(nameof(target));
-            property = property ?? throw new ArgumentNullException(nameof(property));
-            source = source ?? throw new ArgumentNullException(nameof(source));
+            if (target is AvaloniaObject ao)
+            {
+                return property switch
+                {
+                    StyledPropertyBase<T> styled => ao.Bind(styled, source, priority),
+                    DirectPropertyBase<T> direct => ao.Bind(direct, source),
+                    _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
+                };
+            }
 
-            return target.Bind(
-                property,
-                source.ToBindingValue(),
-                priority);
+            throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
         }
 
         /// <summary>
@@ -362,10 +364,8 @@ namespace Avalonia
         /// </summary>
         /// <param name="target">The object.</param>
         /// <param name="property">The property.</param>
-        /// <param name="maxPriority">The maximum priority for the value.</param>
         /// <remarks>
-        /// For styled properties, gets the value of the property if set on the object with a
-        /// priority equal or lower to <paramref name="maxPriority"/>, otherwise
+        /// For styled properties, gets the value of the property excluding animated values, otherwise
         /// <see cref="AvaloniaProperty.UnsetValue"/>. Note that this method does not return
         /// property values that come from inherited or default values.
         /// 
@@ -373,14 +373,13 @@ namespace Avalonia
         /// </remarks>
         public static object? GetBaseValue(
             this IAvaloniaObject target,
-            AvaloniaProperty property,
-            BindingPriority maxPriority)
+            AvaloniaProperty property)
         {
             target = target ?? throw new ArgumentNullException(nameof(target));
             property = property ?? throw new ArgumentNullException(nameof(property));
 
             if (target is AvaloniaObject ao)
-                return property.RouteGetBaseValue(ao, maxPriority);
+                return property.RouteGetBaseValue(ao);
             throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
         }
 
@@ -389,10 +388,8 @@ namespace Avalonia
         /// </summary>
         /// <param name="target">The object.</param>
         /// <param name="property">The property.</param>
-        /// <param name="maxPriority">The maximum priority for the value.</param>
         /// <remarks>
-        /// For styled properties, gets the value of the property if set on the object with a
-        /// priority equal or lower to <paramref name="maxPriority"/>, otherwise
+        /// For styled properties, gets the value of the property excluding animated values, otherwise
         /// <see cref="Optional{T}.Empty"/>. Note that this method does not return property values
         /// that come from inherited or default values.
         /// 
@@ -400,8 +397,7 @@ namespace Avalonia
         /// </remarks>
         public static Optional<T> GetBaseValue<T>(
             this IAvaloniaObject target,
-            AvaloniaProperty<T> property,
-            BindingPriority maxPriority)
+            AvaloniaProperty<T> property)
         {
             target = target ?? throw new ArgumentNullException(nameof(target));
             property = property ?? throw new ArgumentNullException(nameof(property));
@@ -410,7 +406,7 @@ namespace Avalonia
             {
                 return property switch
                 {
-                    StyledPropertyBase<T> styled => ao.GetBaseValue(styled, maxPriority),
+                    StyledPropertyBase<T> styled => ao.GetBaseValue(styled),
                     DirectPropertyBase<T> direct => ao.GetValue(direct),
                     _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
                 };

+ 9 - 6
src/Avalonia.Base/AvaloniaProperty.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using Avalonia.Data;
 using Avalonia.Data.Core;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
 using Avalonia.Utilities;
 
@@ -455,6 +456,12 @@ namespace Avalonia
             return Name;
         }
 
+        /// <summary>
+        /// Creates an effective value for the property.
+        /// </summary>
+        /// <param name="o">The effective value owner.</param>
+        internal abstract EffectiveValue CreateEffectiveValue(AvaloniaObject o);
+
         /// <summary>
         /// Routes an untyped ClearValue call to a typed call.
         /// </summary>
@@ -471,8 +478,7 @@ namespace Avalonia
         /// Routes an untyped GetBaseValue call to a typed call.
         /// </summary>
         /// <param name="o">The object instance.</param>
-        /// <param name="maxPriority">The maximum priority for the value.</param>
-        internal abstract object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority);
+        internal abstract object? RouteGetBaseValue(AvaloniaObject o);
 
         /// <summary>
         /// Routes an untyped SetValue call to a typed call.
@@ -496,12 +502,9 @@ namespace Avalonia
         /// <param name="priority">The priority.</param>
         internal abstract IDisposable RouteBind(
             AvaloniaObject o,
-            IObservable<BindingValue<object?>> source,
+            IObservable<object?> source,
             BindingPriority priority);
 
-        internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent);
-        internal abstract ISetterInstance CreateSetterInstance(IStyleable target, object? value);
-
         /// <summary>
         /// Overrides the metadata for the property on the specified type.
         /// </summary>

+ 11 - 13
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs

@@ -17,6 +17,16 @@ namespace Avalonia
             IsEffectiveValueChange = true;
         }
 
+        internal AvaloniaPropertyChangedEventArgs(
+            IAvaloniaObject sender,
+            BindingPriority priority,
+            bool isEffectiveValueChange)
+        {
+            Sender = sender;
+            Priority = priority;
+            IsEffectiveValueChange = isEffectiveValueChange;
+        }
+
         /// <summary>
         /// Gets the <see cref="AvaloniaObject"/> that the property changed on.
         /// </summary>
@@ -49,20 +59,8 @@ namespace Avalonia
         /// </value>
         public BindingPriority Priority { get; private set; }
 
-        /// <summary>
-        /// Gets a value indicating whether the change represents a change to the effective value of
-        /// the property.
-        /// </summary>
-        /// <remarks>
-        /// This will usually be true, except in
-        /// <see cref="AvaloniaObject.OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs)"/>
-        /// which receives notifications for all changes to property values, whether a value with a higher
-        /// priority is present or not. When this property is false, the change that is being signaled
-        /// has not resulted in a change to the property value on the object.
-        /// </remarks>
-        public bool IsEffectiveValueChange { get; private set; }
+        internal bool IsEffectiveValueChange { get; private set; }
 
-        internal void MarkNonEffectiveValue() => IsEffectiveValueChange = false;
         protected abstract AvaloniaProperty GetProperty();
         protected abstract object? GetOldValue();
         protected abstract object? GetNewValue();

+ 12 - 16
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs

@@ -21,7 +21,18 @@ namespace Avalonia
             Optional<T> oldValue,
             BindingValue<T> newValue,
             BindingPriority priority)
-            : base(sender, priority)
+            : this(sender, property, oldValue, newValue, priority, true)
+        {
+        }
+
+        internal AvaloniaPropertyChangedEventArgs(
+            IAvaloniaObject sender,
+            AvaloniaProperty<T> property,
+            Optional<T> oldValue,
+            BindingValue<T> newValue,
+            BindingPriority priority,
+            bool isEffectiveValueChange)
+            : base(sender, priority, isEffectiveValueChange)
         {
             Property = property;
             OldValue = oldValue;
@@ -39,28 +50,13 @@ namespace Avalonia
         /// <summary>
         /// Gets the old value of the property.
         /// </summary>
-        /// <remarks>
-        /// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is true, returns the
-        /// old value of the property on the object. 
-        /// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is false, returns
-        /// <see cref="Optional{T}.Empty"/>.
-        /// </remarks>
         public new Optional<T> OldValue { get; private set; }
 
         /// <summary>
         /// Gets the new value of the property.
         /// </summary>
-        /// <remarks>
-        /// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is true, returns the
-        /// value of the property on the object.
-        /// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is false returns the
-        /// changed value, or <see cref="Optional{T}.Empty"/> if the value was removed.
-        /// </remarks>
         public new BindingValue<T> NewValue { get; private set; }
 
-        internal void SetOldValue(Optional<T> value) => OldValue = value;
-        internal void SetNewValue(BindingValue<T> value) => NewValue = value;
-
         protected override AvaloniaProperty GetProperty() => Property;
 
         protected override object? GetOldValue() => OldValue.GetValueOrDefault(AvaloniaProperty.UnsetValue);

+ 32 - 0
src/Avalonia.Base/Compatibility/CollectionCompatibilityExtensions.cs

@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System;
+
+#if !NET6_0_OR_GREATER
+internal static class CollectionCompatibilityExtensions
+{
+    public static bool Remove<TKey, TValue>(
+        this Dictionary<TKey, TValue> o,
+        TKey key,
+        [MaybeNullWhen(false)] out TValue value)
+            where TKey : notnull
+    {
+        if (o.TryGetValue(key, out value))
+            return o.Remove(key);
+        return false;
+    }
+
+    public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> o, TKey key, TValue value)
+        where TKey : notnull
+    {
+        if (!o.ContainsKey(key))
+        {
+            o.Add(key, value);
+            return true;
+        }
+
+        return false;
+    }
+}
+#endif

+ 0 - 20
src/Avalonia.Base/Data/BindingNotification.cs

@@ -241,26 +241,6 @@ namespace Avalonia.Data
             _value = value;
         }
 
-        public BindingValue<object?> ToBindingValue()
-        {
-            if (ErrorType == BindingErrorType.None)
-            {
-                return HasValue ? new BindingValue<object?>(Value) : BindingValue<object?>.Unset;
-            }
-            else if (ErrorType == BindingErrorType.Error)
-            {
-                return BindingValue<object?>.BindingError(
-                    Error!,
-                    HasValue ? new Optional<object?>(Value) : Optional<object?>.Empty);
-            }
-            else
-            {
-                return BindingValue<object?>.DataValidationError(
-                    Error!,
-                    HasValue ? new Optional<object?>(Value) : Optional<object?>.Empty);
-            }
-        }
-
         /// <inheritdoc/>
         public override string ToString()
         {

+ 5 - 0
src/Avalonia.Base/Data/BindingPriority.cs

@@ -35,6 +35,11 @@ namespace Avalonia.Data
         /// A style binding.
         /// </summary>
         Style,
+        
+        /// <summary>
+        /// The value is inherited from an ancestor element.
+        /// </summary>
+        Inherited,
 
         /// <summary>
         /// The binding is uninitialized.

+ 66 - 64
src/Avalonia.Base/Data/BindingValue.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
 using Avalonia.Utilities;
 
@@ -230,19 +231,77 @@ namespace Avalonia.Data
 
         /// <summary>
         /// Creates a <see cref="BindingValue{T}"/> from an object, handling the special values
-        /// <see cref="AvaloniaProperty.UnsetValue"/> and <see cref="BindingOperations.DoNothing"/>.
+        /// <see cref="AvaloniaProperty.UnsetValue"/>, <see cref="BindingOperations.DoNothing"/> and
+        /// <see cref="BindingNotification"/>.
         /// </summary>
         /// <param name="value">The untyped value.</param>
         /// <returns>The typed binding value.</returns>
         public static BindingValue<T> FromUntyped(object? value)
         {
-            return value switch
+            return FromUntyped(value, typeof(T));
+        }
+
+        /// <summary>
+        /// Creates a <see cref="BindingValue{T}"/> from an object, handling the special values
+        /// <see cref="AvaloniaProperty.UnsetValue"/>, <see cref="BindingOperations.DoNothing"/> and
+        /// <see cref="BindingNotification"/>.
+        /// </summary>
+        /// <param name="value">The untyped value.</param>
+        /// <param name="targetType">The runtime target type.</param>
+        /// <returns>The typed binding value.</returns>
+        public static BindingValue<T> FromUntyped(object? value, Type targetType)
+        {
+            if (value == AvaloniaProperty.UnsetValue)
+                return Unset;
+            else if (value == BindingOperations.DoNothing)
+                return DoNothing;
+
+            var type = BindingValueType.Value;
+            T? v = default;
+            Exception? error = null;
+            List<Exception>? errors = null;
+
+            if (value is BindingNotification n)
             {
-                UnsetValueType _ => Unset,
-                DoNothingType _ => DoNothing,
-                BindingNotification n => n.ToBindingValue().Cast<T>(),
-                _ => new BindingValue<T>((T)value!)
-            };
+                error = n.Error;
+                type = n.ErrorType switch
+                {
+                    BindingErrorType.Error => BindingValueType.BindingError,
+                    BindingErrorType.DataValidationError => BindingValueType.DataValidationError,
+                    _ => BindingValueType.Value,
+                };
+
+                if (n.HasValue)
+                    type |= BindingValueType.HasValue;
+                value = n.Value;
+            }
+
+            if ((type & BindingValueType.HasValue) != 0)
+            {
+                if (TypeUtilities.TryConvertImplicit(targetType, value, out var typed))
+                    v = (T)typed!;
+                else
+                {
+                    var e = new InvalidCastException(
+                        $"Unable to convert object '{value ?? "(null)"}' " +
+                        $"of type '{value?.GetType()}' to type '{targetType}'.");
+
+                    if (error is null)
+                        error = e;
+                    else
+                    {
+                        errors ??= new List<Exception>() { error };
+                        errors.Add(e);
+                    }
+
+                    type = BindingValueType.BindingError;
+                }
+            }
+
+            if (errors is not null)
+                error = new AggregateException(errors);
+
+            return new BindingValue<T>(type, v, error);
         }
 
         /// <summary>
@@ -372,61 +431,4 @@ namespace Avalonia.Data
             }
         }
     }
-
-    public static class BindingValueExtensions
-    {
-        /// <summary>
-        /// Casts the type of a <see cref="BindingValue{T}"/> using only the C# cast operator.
-        /// </summary>
-        /// <typeparam name="T">The target type.</typeparam>
-        /// <param name="value">The binding value.</param>
-        /// <returns>The cast value.</returns>
-        public static BindingValue<T> Cast<T>(this BindingValue<object?> value)
-        {
-            return value.Type switch
-            {
-                BindingValueType.DoNothing => BindingValue<T>.DoNothing,
-                BindingValueType.UnsetValue => BindingValue<T>.Unset,
-                BindingValueType.Value => new BindingValue<T>((T)value.Value!),
-                BindingValueType.BindingError => BindingValue<T>.BindingError(value.Error!),
-                BindingValueType.BindingErrorWithFallback => BindingValue<T>.BindingError(
-                        value.Error!,
-                        (T)value.Value!),
-                BindingValueType.DataValidationError => BindingValue<T>.DataValidationError(value.Error!),
-                BindingValueType.DataValidationErrorWithFallback => BindingValue<T>.DataValidationError(
-                        value.Error!,
-                        (T)value.Value!),
-                _ => throw new NotSupportedException("Invalid BindingValue type."),
-            };
-        }
-
-        /// <summary>
-        /// Casts the type of a <see cref="BindingValue{T}"/> using the implicit conversions
-        /// allowed by the C# language.
-        /// </summary>
-        /// <typeparam name="T">The target type.</typeparam>
-        /// <param name="value">The binding value.</param>
-        /// <returns>The cast value.</returns>
-        /// <remarks>
-        /// Note that this method uses reflection and as such may be slow.
-        /// </remarks>
-        public static BindingValue<T> Convert<T>(this BindingValue<object?> value)
-        {
-            return value.Type switch
-            {
-                BindingValueType.DoNothing => BindingValue<T>.DoNothing,
-                BindingValueType.UnsetValue => BindingValue<T>.Unset,
-                BindingValueType.Value => new BindingValue<T>(TypeUtilities.ConvertImplicit<T>(value.Value!)),
-                BindingValueType.BindingError => BindingValue<T>.BindingError(value.Error!),
-                BindingValueType.BindingErrorWithFallback => BindingValue<T>.BindingError(
-                        value.Error!,
-                        TypeUtilities.ConvertImplicit<T>(value.Value!)),
-                BindingValueType.DataValidationError => BindingValue<T>.DataValidationError(value.Error!),
-                BindingValueType.DataValidationErrorWithFallback => BindingValue<T>.DataValidationError(
-                        value.Error!,
-                        TypeUtilities.ConvertImplicit<T>(value.Value!)),
-                _ => throw new NotSupportedException("Invalid BindingValue type."),
-            };
-        }
-    }
 }

+ 5 - 4
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@@ -52,6 +52,7 @@ namespace Avalonia.Data.Core
         private static readonly object UninitializedValue = new object();
         private readonly ExpressionNode _node;
         private object? _root;
+        private Func<object?>? _rootGetter;
         private IDisposable? _rootSubscription;
         private WeakReference<object?>? _value;
         private IReadOnlyList<ITransformNode>? _transformNodes;
@@ -109,11 +110,9 @@ namespace Avalonia.Data.Core
             IObservable<Unit> update,
             string? description)
         {
-            _ = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter));
-
             Description = description;
-            _node = node ?? throw new ArgumentNullException(nameof(rootGetter));
-            _node.Target = new WeakReference<object?>(rootGetter());
+            _rootGetter = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter));
+            _node = node ?? throw new ArgumentNullException(nameof(node));
             _root = update.Select(x => rootGetter());
         }
 
@@ -263,6 +262,8 @@ namespace Avalonia.Data.Core
         protected override void Initialize()
         {
             _value = null;
+            if (_rootGetter is not null)
+                _node.Target = new WeakReference<object?>(_rootGetter());
             _node.Subscribe(ValueChanged);
             StartRoot();
         }

+ 15 - 35
src/Avalonia.Base/DirectPropertyBase.cs

@@ -1,5 +1,6 @@
 using System;
 using Avalonia.Data;
+using Avalonia.PropertyStore;
 using Avalonia.Reactive;
 using Avalonia.Styling;
 
@@ -105,6 +106,11 @@ namespace Avalonia
             base.OverrideMetadata(type, metadata);
         }
 
+        internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o)
+        {
+            throw new InvalidOperationException("Cannot create EffectiveValue for direct property.");
+        }
+
         /// <inheritdoc/>
         internal override void RouteClearValue(AvaloniaObject o)
         {
@@ -117,7 +123,7 @@ namespace Avalonia
             return o.GetValue<TValue>(this);
         }
 
-        internal override object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority)
+        internal override object? RouteGetBaseValue(AvaloniaObject o)
         {
             return o.GetValue<TValue>(this);
         }
@@ -146,44 +152,18 @@ namespace Avalonia
             return null;
         }
 
-        /// <inheritdoc/>
+        /// <summary>
+        /// Routes an untyped Bind call to a typed call.
+        /// </summary>
+        /// <param name="o">The object instance.</param>
+        /// <param name="source">The binding source.</param>
+        /// <param name="priority">The priority.</param>
         internal override IDisposable RouteBind(
             AvaloniaObject o,
-            IObservable<BindingValue<object?>> source,
+            IObservable<object?> source,
             BindingPriority priority)
         {
-            var adapter = TypedBindingAdapter<TValue>.Create(o, this, source);
-            return o.Bind<TValue>(this, adapter);
-        }
-
-        internal override void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent)
-        {
-            throw new NotSupportedException("Direct properties do not support inheritance.");
-        }
-
-        internal override ISetterInstance CreateSetterInstance(IStyleable target, object? value)
-        {
-            if (value is IBinding binding)
-            {
-                return new PropertySetterBindingInstance<TValue>(
-                    target,
-                    this,
-                    binding);
-            }
-            else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
-            {
-                return new PropertySetterTemplateInstance<TValue>(
-                    target,
-                    this,
-                    template);
-            }
-            else
-            {
-                return new PropertySetterInstance<TValue>(
-                    target,
-                    this,
-                    (TValue)value!);
-            }
+            return o.Bind(this, source);
         }
     }
 }

+ 7 - 0
src/Avalonia.Base/IStyledPropertyAccessor.cs

@@ -15,5 +15,12 @@ namespace Avalonia
         /// The default value.
         /// </returns>
         object? GetDefaultValue(Type type);
+
+        /// <summary>
+        /// Validates the specified property value.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns>True if the value is valid, otherwise false.</returns>
+        bool ValidateValue(object? value);
     }
 }

+ 1 - 7
src/Avalonia.Base/Input/MouseDevice.cs

@@ -193,18 +193,12 @@ namespace Avalonia.Input
             PointerPointProperties props,
             Vector delta, KeyModifiers inputModifiers, IInputElement? hitTest)
         {
+            var rawDelta = delta;
             device = device ?? throw new ArgumentNullException(nameof(device));
             root = root ?? throw new ArgumentNullException(nameof(root));
 
             var source = _pointer.Captured ?? hitTest;
 
-            // KeyModifiers.Shift should scroll in horizontal direction. This does not work on every platform. 
-            // If Shift-Key is pressed and X is close to 0 we swap the Vector.
-            if (inputModifiers == KeyModifiers.Shift && MathUtilities.IsZero(delta.X))
-            {
-                delta = new Vector(delta.Y, delta.X);
-            }
-
             if (source is not null)
             {
                 var e = new PointerWheelEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta);

+ 1 - 1
src/Avalonia.Base/Input/PointerWheelEventArgs.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Input
 
         internal PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual,
             Point rootVisualPosition, ulong timestamp,
-            PointerPointProperties properties, KeyModifiers modifiers, Vector delta) 
+            PointerPointProperties properties, KeyModifiers modifiers, Vector delta)
             : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition,
                 timestamp, properties, modifiers)
         {

+ 25 - 0
src/Avalonia.Base/PropertyStore/AvaloniaPropertyDictionaryPool.cs

@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using Avalonia.Utilities;
+
+namespace Avalonia.PropertyStore
+{
+    internal static class AvaloniaPropertyDictionaryPool<TValue>
+    {
+        private const int MaxPoolSize = 4;
+        private static readonly Stack<AvaloniaPropertyDictionary<TValue>> _pool = new();
+
+        public static AvaloniaPropertyDictionary<TValue> Get()
+        {
+            return _pool.Count == 0 ? new() : _pool.Pop();
+        }
+
+        public static void Release(AvaloniaPropertyDictionary<TValue> dictionary)
+        {
+            if (_pool.Count < MaxPoolSize)
+            {
+                dictionary.Clear();
+                _pool.Push(dictionary);
+            }
+        }
+    }
+}

+ 0 - 154
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@@ -1,154 +0,0 @@
-using System;
-using Avalonia.Data;
-using Avalonia.Threading;
-
-namespace Avalonia.PropertyStore
-{
-    /// <summary>
-    /// Represents an untyped interface to <see cref="BindingEntry{T}"/>.
-    /// </summary>
-    internal interface IBindingEntry : IBatchUpdate, IPriorityValueEntry, IDisposable
-    {
-        void Start(bool ignoreBatchUpdate);
-    }
-
-    /// <summary>
-    /// Stores a binding in a <see cref="ValueStore"/> or <see cref="PriorityValue{T}"/>.
-    /// </summary>
-    /// <typeparam name="T">The property type.</typeparam>
-    internal class BindingEntry<T> : IBindingEntry, IPriorityValueEntry<T>, IObserver<BindingValue<T>>
-    {
-        private readonly AvaloniaObject _owner;
-        private ValueOwner<T> _sink;
-        private IDisposable? _subscription;
-        private bool _isSubscribed;
-        private bool _batchUpdate;
-        private Optional<T> _value;
-
-        public BindingEntry(
-            AvaloniaObject owner,
-            StyledPropertyBase<T> property,
-            IObservable<BindingValue<T>> source,
-            BindingPriority priority,
-            ValueOwner<T> sink)
-        {
-            _owner = owner;
-            Property = property;
-            Source = source;
-            Priority = priority;
-            _sink = sink;
-        }
-
-        public StyledPropertyBase<T> Property { get; }
-        public BindingPriority Priority { get; private set; }
-        public IObservable<BindingValue<T>> Source { get; }
-        Optional<object?> IValue.GetValue() => _value.ToObject();
-
-        public void BeginBatchUpdate() => _batchUpdate = true;
-
-        public void EndBatchUpdate()
-        {
-            _batchUpdate = false;
-
-            if (_sink.IsValueStore)
-                Start();
-        }
-
-        public Optional<T> GetValue(BindingPriority maxPriority)
-        {
-            return Priority >= maxPriority ? _value : Optional<T>.Empty;
-        }
-
-        public void Dispose()
-        {
-            _subscription?.Dispose();
-            _subscription = null;
-            OnCompleted();
-        }
-
-        public void OnCompleted()
-        {
-            var oldValue = _value;
-            _value = default;
-            Priority = BindingPriority.Unset;
-            _isSubscribed = false;
-            _sink.Completed(Property, this, oldValue);
-        }
-
-        public void OnError(Exception error)
-        {
-            throw new NotImplementedException("BindingEntry.OnError is not implemented", error);
-        }
-
-        public void OnNext(BindingValue<T> value)
-        {
-            if (Dispatcher.UIThread.CheckAccess())
-            {
-                UpdateValue(value); 
-            }
-            else
-            {
-                // To avoid allocating closure in the outer scope we need to capture variables
-                // locally. This allows us to skip most of the allocations when on UI thread.
-                var instance = this;
-                var newValue = value;
-
-                Dispatcher.UIThread.Post(() => instance.UpdateValue(newValue));
-            }
-        }
-
-        public void Start() => Start(false);
-
-        public void Start(bool ignoreBatchUpdate)
-        {
-            // We can't use _subscription to check whether we're subscribed because it won't be set
-            // until Subscribe has finished, which will be too late to prevent reentrancy. In addition
-            // don't re-subscribe to completed/disposed bindings (indicated by Unset priority).
-            if (!_isSubscribed &&
-                Priority != BindingPriority.Unset &&
-                (!_batchUpdate || ignoreBatchUpdate))
-            {
-                _isSubscribed = true;
-                _subscription = Source.Subscribe(this);
-            }
-        }
-
-        public void Reparent(PriorityValue<T> parent) => _sink = new(parent);
-
-        public void RaiseValueChanged(
-            AvaloniaObject owner,
-            AvaloniaProperty property,
-            Optional<object?> oldValue,
-            Optional<object?> newValue)
-        {
-            owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
-                owner,
-                (AvaloniaProperty<T>)property,
-                oldValue.Cast<T>(),
-                newValue.Cast<T>(),
-                Priority));
-        }
-
-        private void UpdateValue(BindingValue<T> value)
-        {
-            if (value.HasValue && Property.ValidateValue?.Invoke(value.Value) == false)
-            {
-                value = Property.GetDefaultValue(_owner.GetType());
-            }
-
-            if (value.Type == BindingValueType.DoNothing)
-            {
-                return;
-            }
-
-            var old = _value;
-
-            if (value.Type != BindingValueType.DataValidationError)
-            {
-                _value = value.ToOptional();
-            }
-
-            _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(_owner, Property, old, value, Priority));
-        }
-    }
-}

+ 148 - 0
src/Avalonia.Base/PropertyStore/BindingEntryBase.cs

@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Disposables;
+using Avalonia.Data;
+
+namespace Avalonia.PropertyStore
+{
+    internal abstract class BindingEntryBase<TValue, TSource> : IValueEntry<TValue>,
+        IObserver<TSource>,
+        IObserver<BindingValue<TSource>>,
+        IDisposable
+    {
+        private static readonly IDisposable s_creating = Disposable.Empty;
+        private static readonly IDisposable s_creatingQuiet = Disposable.Create(() => { });
+        private IDisposable? _subscription;
+        private bool _hasValue;
+        private TValue? _value;
+
+        protected BindingEntryBase(
+            ValueFrame frame,
+            AvaloniaProperty property,
+            IObservable<BindingValue<TSource>> source)
+        {
+            Frame = frame;
+            Source = source;
+            Property = property;
+        }
+
+        protected BindingEntryBase(
+            ValueFrame frame,
+            AvaloniaProperty property,
+            IObservable<TSource> source)
+        {
+            Frame = frame;
+            Source = source;
+            Property = property;
+        }
+
+        public bool HasValue
+        {
+            get
+            {
+                Start(produceValue: false);
+                return _hasValue;
+            }
+        }
+
+        public bool IsSubscribed => _subscription is not null;
+        public AvaloniaProperty Property { get; }
+        AvaloniaProperty IValueEntry.Property => Property;
+        protected ValueFrame Frame { get; }
+        protected object Source { get; }
+
+        public void Dispose()
+        {
+            Unsubscribe();
+            BindingCompleted();
+        }
+
+        public TValue GetValue()
+        {
+            Start(produceValue: false);
+            if (!_hasValue)
+                throw new AvaloniaInternalException("The binding entry has no value.");
+            return _value!;
+        }
+
+        public void Start() => Start(true);
+
+        public void OnCompleted() => BindingCompleted();
+        public void OnError(Exception error) => BindingCompleted();
+        public void OnNext(TSource value) => SetValue(ConvertAndValidate(value));
+        public void OnNext(BindingValue<TSource> value) => SetValue(ConvertAndValidate(value));
+
+        public virtual void Unsubscribe()
+        {
+            _subscription?.Dispose();
+            _subscription = null;
+        }
+
+        object? IValueEntry.GetValue()
+        {
+            Start(produceValue: false);
+            if (!_hasValue)
+                throw new AvaloniaInternalException("The BindingEntry<T> has no value.");
+            return _value!;
+        }
+
+        protected abstract BindingValue<TValue> ConvertAndValidate(TSource value);
+        protected abstract BindingValue<TValue> ConvertAndValidate(BindingValue<TSource> value);
+
+        protected virtual void Start(bool produceValue)
+        {
+            if (_subscription is not null)
+                return;
+
+            _subscription = produceValue ? s_creating : s_creatingQuiet;
+            _subscription = Source switch
+            {
+                IObservable<BindingValue<TSource>> bv => bv.Subscribe(this),
+                IObservable<TSource> b => b.Subscribe(this),
+                _ => throw new AvaloniaInternalException("Unexpected binding source."),
+            };
+        }
+
+        private void ClearValue()
+        {
+            if (_hasValue)
+            {
+                _hasValue = false;
+                _value = default;
+                if (_subscription is not null)
+                    Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority);
+            }
+        }
+
+        private void SetValue(BindingValue<TValue> value)
+        {
+            if (Frame.Owner is null)
+                return;
+
+            LoggingUtils.LogIfNecessary(Frame.Owner.Owner, Property, value);
+
+            if (value.HasValue)
+            {
+                if (!_hasValue || !EqualityComparer<TValue>.Default.Equals(_value, value.Value))
+                {
+                    _value = value.Value;
+                    _hasValue = true;
+                    if (_subscription is not null && _subscription != s_creatingQuiet)
+                        Frame.Owner?.OnBindingValueChanged(this, Frame.Priority);
+                }
+            }
+            else if (value.Type != BindingValueType.DoNothing)
+            {
+                ClearValue();
+                if (_subscription is not null && _subscription != s_creatingQuiet)
+                    Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority);
+            }
+        }
+
+        private void BindingCompleted()
+        {
+            _subscription = null;
+            Frame.OnBindingCompleted(this);
+        }
+    }
+}

+ 0 - 82
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

@@ -1,82 +0,0 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
-using Avalonia.Data;
-
-namespace Avalonia.PropertyStore
-{
-    /// <summary>
-    /// Represents an untyped interface to <see cref="ConstantValueEntry{T}"/>.
-    /// </summary>
-    internal interface IConstantValueEntry : IPriorityValueEntry, IDisposable
-    {
-    }
-
-    /// <summary>
-    /// Stores a value with a priority in a <see cref="ValueStore"/> or
-    /// <see cref="PriorityValue{T}"/>.
-    /// </summary>
-    /// <typeparam name="T">The property type.</typeparam>
-    internal class ConstantValueEntry<T> : IPriorityValueEntry<T>, IConstantValueEntry
-    {
-        private ValueOwner<T> _sink;
-        private Optional<T> _value;
-
-        public ConstantValueEntry(
-            StyledPropertyBase<T> property,
-            T value,
-            BindingPriority priority,
-            ValueOwner<T> sink)
-        {
-            Property = property;
-            _value = value;
-            Priority = priority;
-            _sink = sink;
-        }
-
-        public ConstantValueEntry(
-            StyledPropertyBase<T> property,
-            Optional<T> value,
-            BindingPriority priority,
-            ValueOwner<T> sink)
-        {
-            Property = property;
-            _value = value;
-            Priority = priority;
-            _sink = sink;
-        }
-
-        public StyledPropertyBase<T> Property { get; }
-        public BindingPriority Priority { get; private set; }
-        Optional<object?> IValue.GetValue() => _value.ToObject();
-
-        public Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation)
-        {
-            return Priority >= maxPriority ? _value : Optional<T>.Empty;
-        }
-
-        public void Dispose()
-        {
-            var oldValue = _value;
-            _value = default;
-            Priority = BindingPriority.Unset;
-            _sink.Completed(Property, this, oldValue);
-        }
-
-        public void Reparent(PriorityValue<T> sink) => _sink = new(sink);
-        public void Start() { }
-
-        public void RaiseValueChanged(
-            AvaloniaObject owner,
-            AvaloniaProperty property,
-            Optional<object?> oldValue,
-            Optional<object?> newValue)
-        {
-            owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
-                owner,
-                (AvaloniaProperty<T>)property,
-                oldValue.Cast<T>(),
-                newValue.Cast<T>(),
-                Priority));
-        }
-    }
-}

+ 76 - 0
src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs

@@ -0,0 +1,76 @@
+using System;
+using Avalonia.Data;
+using Avalonia.Threading;
+
+namespace Avalonia.PropertyStore
+{
+    internal class DirectBindingObserver<T> : IObserver<T>,
+        IObserver<BindingValue<T>>,
+        IDisposable
+    {
+        private readonly ValueStore _owner;
+        private IDisposable? _subscription;
+
+        public DirectBindingObserver(ValueStore owner, DirectPropertyBase<T> property)
+        {
+            _owner = owner;
+            Property = property;
+        }
+
+        public DirectPropertyBase<T> Property { get;}
+
+        public void Start(IObservable<T> source)
+        {
+            _subscription = source.Subscribe(this);
+        }
+
+        public void Start(IObservable<BindingValue<T>> source)
+        {
+            _subscription = source.Subscribe(this);
+        }
+
+        public void Dispose()
+        {
+            _subscription?.Dispose();
+            _subscription = null;
+            _owner.OnLocalValueBindingCompleted(Property, this);
+        }
+
+        public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this);
+        public void OnError(Exception error) => OnCompleted();
+
+        public void OnNext(T value)
+        {
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                _owner.Owner.SetDirectValueUnchecked<T>(Property, value);
+            }
+            else
+            {
+                // To avoid allocating closure in the outer scope we need to capture variables
+                // locally. This allows us to skip most of the allocations when on UI thread.
+                var instance = _owner.Owner;
+                var property = Property;
+                var newValue = value;
+                Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue));
+            }
+        }
+
+        public void OnNext(BindingValue<T> value)
+        {
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                _owner.Owner.SetDirectValueUnchecked<T>(Property, value);
+            }
+            else
+            {
+                // To avoid allocating closure in the outer scope we need to capture variables
+                // locally. This allows us to skip most of the allocations when on UI thread.
+                var instance = _owner.Owner;
+                var property = Property;
+                var newValue = value;
+                Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue));
+            }
+        }
+    }
+}

+ 55 - 0
src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs

@@ -0,0 +1,55 @@
+using System;
+using Avalonia.Data;
+using Avalonia.Threading;
+
+namespace Avalonia.PropertyStore
+{
+    internal class DirectUntypedBindingObserver<T> : IObserver<object?>,
+        IDisposable
+    {
+        private readonly ValueStore _owner;
+        private IDisposable? _subscription;
+
+        public DirectUntypedBindingObserver(ValueStore owner, DirectPropertyBase<T> property)
+        {
+            _owner = owner;
+            Property = property;
+        }
+
+        public DirectPropertyBase<T> Property { get;}
+
+        public void Start(IObservable<object?> source)
+        {
+            _subscription = source.Subscribe(this);
+        }
+
+        public void Dispose()
+        {
+            _subscription?.Dispose();
+            _subscription = null;
+            _owner.OnLocalValueBindingCompleted(Property, this);
+        }
+
+        public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this);
+        public void OnError(Exception error) => OnCompleted();
+
+        public void OnNext(object? value)
+        {
+            var typed = BindingValue<T>.FromUntyped(value);
+
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                _owner.Owner.SetDirectValueUnchecked<T>(Property, typed);
+            }
+            else
+            {
+                // To avoid allocating closure in the outer scope we need to capture variables
+                // locally. This allows us to skip most of the allocations when on UI thread.
+                var instance = _owner.Owner;
+                var property = Property;
+                var newValue = value;
+                Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, typed));
+            }
+        }
+    }
+}

+ 168 - 0
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@@ -0,0 +1,168 @@
+using System.Diagnostics;
+using Avalonia.Data;
+
+namespace Avalonia.PropertyStore
+{
+    /// <summary>
+    /// Represents the active value for a property in a <see cref="ValueStore"/>.
+    /// </summary>
+    /// <remarks>
+    /// This class is an abstract base for the generic <see cref="EffectiveValue{T}"/>.
+    /// </remarks>
+    internal abstract class EffectiveValue
+    {
+        private IValueEntry? _valueEntry;
+        private IValueEntry? _baseValueEntry;
+
+        /// <summary>
+        /// Gets the current effective value as a boxed value.
+        /// </summary>
+        public object? Value => GetBoxedValue();
+
+        /// <summary>
+        /// Gets the priority of the current effective value.
+        /// </summary>
+        public BindingPriority Priority { get; protected set; }
+
+        /// <summary>
+        /// Gets the priority of the current base value.
+        /// </summary>
+        public BindingPriority BasePriority { get; protected set; }
+
+        /// <summary>
+        /// Begins a reevaluation pass on the effective value.
+        /// </summary>
+        /// <param name="clearLocalValue">
+        /// Determines whether any current local value should be cleared.
+        /// </param>
+        /// <remarks>
+        /// This method resets the <see cref="Priority"/> and <see cref="BasePriority"/> properties
+        /// to Unset, pending reevaluation.
+        /// </remarks>
+        public void BeginReevaluation(bool clearLocalValue = false)
+        {
+            if (clearLocalValue || Priority != BindingPriority.LocalValue)
+                Priority = BindingPriority.Unset;
+            if (clearLocalValue || BasePriority != BindingPriority.LocalValue)
+                BasePriority = BindingPriority.Unset;
+        }
+
+        /// <summary>
+        /// Ends a reevaluation pass on the effective value.
+        /// </summary>
+        /// <remarks>
+        /// This method unsubscribes from any unused value entries.
+        /// </remarks>
+        public void EndReevaluation()
+        {
+            if (Priority == BindingPriority.Unset)
+            {
+                _valueEntry?.Unsubscribe();
+                _valueEntry = null;
+            }
+
+            if (BasePriority == BindingPriority.Unset)
+            {
+                _baseValueEntry?.Unsubscribe();
+                _baseValueEntry = null;
+            }
+        }
+
+        /// <summary>
+        /// Sets the value and base value for a non-LocalValue priority, raising 
+        /// <see cref="AvaloniaObject.PropertyChanged"/> where necessary.
+        /// </summary>
+        /// <param name="owner">The associated value store.</param>
+        /// <param name="value">The new value of the property.</param>
+        /// <param name="priority">The priority of the new value.</param>
+        public abstract void SetAndRaise(
+            ValueStore owner,
+            IValueEntry value,
+            BindingPriority priority);
+
+        /// <summary>
+        /// Raises <see cref="AvaloniaObject.PropertyChanged"/> in response to an inherited value
+        /// change.
+        /// </summary>
+        /// <param name="owner">The owner object.</param>
+        /// <param name="property">The property being changed.</param>
+        /// <param name="oldValue">The old value of the property.</param>
+        /// <param name="newValue">The new value of the property.</param>
+        public abstract void RaiseInheritedValueChanged(
+            AvaloniaObject owner,
+            AvaloniaProperty property,
+            EffectiveValue? oldValue,
+            EffectiveValue? newValue);
+
+        /// <summary>
+        /// Removes the current animation value and reverts to the base value, raising
+        /// <see cref="AvaloniaObject.PropertyChanged"/> where necessary.
+        /// </summary>
+        /// <param name="owner">The associated value store.</param>
+        /// <param name="property">The property being changed.</param>
+        public abstract void RemoveAnimationAndRaise(ValueStore owner, AvaloniaProperty property);
+
+        /// <summary>
+        /// Coerces the property value.
+        /// </summary>
+        /// <param name="owner">The associated value store.</param>
+        /// <param name="property">The property to coerce.</param>
+        public abstract void CoerceValue(ValueStore owner, AvaloniaProperty property);
+
+        /// <summary>
+        /// Disposes the effective value, raising <see cref="AvaloniaObject.PropertyChanged"/>
+        /// where necessary.
+        /// </summary>
+        /// <param name="owner">The associated value store.</param>
+        /// <param name="property">The property being cleared.</param>
+        public abstract void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property);
+
+        protected abstract object? GetBoxedValue();
+
+        protected void UpdateValueEntry(IValueEntry? entry, BindingPriority priority)
+        {
+            Debug.Assert(priority != BindingPriority.LocalValue);
+
+            if (priority <= BindingPriority.Animation)
+            {
+                // If we've received an animation value and the current value is a non-animation
+                // value, then the current entry becomes our base entry.
+                if (Priority > BindingPriority.LocalValue && Priority < BindingPriority.Inherited)
+                {
+                    Debug.Assert(_valueEntry is not null);
+                    _baseValueEntry = _valueEntry;
+                    _valueEntry = null;
+                }
+
+                if (_valueEntry != entry)
+                {
+                    _valueEntry?.Unsubscribe();
+                    _valueEntry = entry;
+                }
+            }
+            else if (Priority <= BindingPriority.Animation)
+            {
+                // We've received a non-animation value and have an active animation value, so the
+                // new entry becomes our base entry.
+                if (_baseValueEntry != entry)
+                {
+                    _baseValueEntry?.Unsubscribe();
+                    _baseValueEntry = entry;
+                }
+            }
+            else if (_valueEntry != entry)
+            {
+                // Both the current value and the new value are non-animation values, so the new
+                // entry replaces the existing entry.
+                _valueEntry?.Unsubscribe();
+                _valueEntry = entry;
+            }
+        }
+
+        protected void UnsubscribeValueEntries()
+        {
+            _valueEntry?.Unsubscribe();
+            _baseValueEntry?.Unsubscribe();
+        }
+    }
+}

+ 270 - 0
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@@ -0,0 +1,270 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Data;
+
+namespace Avalonia.PropertyStore
+{
+    /// <summary>
+    /// Represents the active value for a property in a <see cref="ValueStore"/>.
+    /// </summary>
+    /// <remarks>
+    /// Stores the active value in an <see cref="AvaloniaObject"/>'s <see cref="ValueStore"/>
+    /// for a single property, when the value is not inherited or unset/default.
+    /// </remarks>
+    internal sealed class EffectiveValue<T> : EffectiveValue
+    {
+        private readonly StyledPropertyMetadata<T> _metadata;
+        private T? _baseValue;
+        private UncommonFields? _uncommon;
+
+        public EffectiveValue(AvaloniaObject owner, StyledPropertyBase<T> property)
+        {
+            Priority = BindingPriority.Unset;
+            BasePriority = BindingPriority.Unset;
+            _metadata = property.GetMetadata(owner.GetType());
+
+            var value = _metadata.DefaultValue;
+
+            if (property.HasCoercion && _metadata.CoerceValue is { } coerce)
+            {
+                _uncommon = new()
+                {
+                    _coerce = coerce,
+                    _uncoercedValue = value,
+                    _uncoercedBaseValue = value,
+                };
+
+                Value = coerce(owner, value);
+            }
+            else
+            {
+                Value = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets the current effective value.
+        /// </summary>
+        public new T Value { get; private set; }
+
+        public override void SetAndRaise(
+            ValueStore owner,
+            IValueEntry value, 
+            BindingPriority priority)
+        {
+            Debug.Assert(priority != BindingPriority.LocalValue);
+            UpdateValueEntry(value, priority);
+
+            SetAndRaiseCore(owner,  (StyledPropertyBase<T>)value.Property, GetValue(value), priority);
+        }
+
+        public void SetLocalValueAndRaise(
+            ValueStore owner,
+            StyledPropertyBase<T> property,
+            T value)
+        {
+            SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue);
+        }
+
+        public bool TryGetBaseValue([MaybeNullWhen(false)] out T value)
+        {
+            value = _baseValue!;
+            return BasePriority != BindingPriority.Unset;
+        }
+
+        public override void RaiseInheritedValueChanged(
+            AvaloniaObject owner,
+            AvaloniaProperty property,
+            EffectiveValue? oldValue,
+            EffectiveValue? newValue)
+        {
+            Debug.Assert(oldValue is not null || newValue is not null);
+
+            var p = (StyledPropertyBase<T>)property;
+            var o = oldValue is not null ? ((EffectiveValue<T>)oldValue).Value : _metadata.DefaultValue;
+            var n = newValue is not null ? ((EffectiveValue<T>)newValue).Value : _metadata.DefaultValue;
+            var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset;
+
+            if (!EqualityComparer<T>.Default.Equals(o, n))
+            {
+                owner.RaisePropertyChanged(p, o, n, priority, true);
+            }
+        }
+
+        public override void RemoveAnimationAndRaise(ValueStore owner, AvaloniaProperty property)
+        {
+            Debug.Assert(Priority != BindingPriority.Animation);
+            Debug.Assert(BasePriority != BindingPriority.Unset);
+            UpdateValueEntry(null, BindingPriority.Animation);
+            SetAndRaiseCore(owner, (StyledPropertyBase<T>)property, _baseValue!, BasePriority);
+        }
+
+        public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
+        {
+            if (_uncommon is null)
+                return;
+            SetAndRaiseCore(
+                owner, 
+                (StyledPropertyBase<T>)property, 
+                _uncommon._uncoercedValue!, 
+                Priority, 
+                _uncommon._uncoercedBaseValue!,
+                BasePriority);
+        }
+
+        public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property)
+        {
+            UnsubscribeValueEntries();
+            DisposeAndRaiseUnset(owner, (StyledPropertyBase<T>)property);
+        }
+
+        public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase<T> property)
+        {
+            BindingPriority priority;
+            T oldValue;
+
+            if (property.Inherits && owner.TryGetInheritedValue(property, out var i))
+            {
+                oldValue = ((EffectiveValue<T>)i).Value;
+                priority = BindingPriority.Inherited;
+            }
+            else
+            {
+                oldValue = _metadata.DefaultValue;
+                priority = BindingPriority.Unset;
+            }
+
+            if (!EqualityComparer<T>.Default.Equals(oldValue, Value))
+            {
+                owner.Owner.RaisePropertyChanged(property, Value, oldValue, priority, true);
+                if (property.Inherits)
+                    owner.OnInheritedEffectiveValueDisposed(property, Value);
+            }
+        }
+
+        protected override object? GetBoxedValue() => Value;
+        
+        private static T GetValue(IValueEntry entry)
+        {
+            if (entry is IValueEntry<T> typed)
+                return typed.GetValue();
+            else
+                return (T)entry.GetValue()!;
+        }
+
+        private void SetAndRaiseCore(
+            ValueStore owner,
+            StyledPropertyBase<T> property,
+            T value,
+            BindingPriority priority)
+        {
+            Debug.Assert(priority < BindingPriority.Inherited);
+
+            var oldValue = Value;
+            var valueChanged = false;
+            var baseValueChanged = false;
+            var v = value;
+
+            if (_uncommon?._coerce is { } coerce)
+                v = coerce(owner.Owner, value);
+
+            if (priority <= Priority)
+            {
+                valueChanged = !EqualityComparer<T>.Default.Equals(Value, v);
+                Value = v;
+                Priority = priority;
+                if (_uncommon is not null)
+                    _uncommon._uncoercedValue = value;
+            }
+
+            if (priority <= BasePriority && priority >= BindingPriority.LocalValue)
+            {
+                baseValueChanged = !EqualityComparer<T>.Default.Equals(_baseValue, v);
+                _baseValue = v;
+                BasePriority = priority;
+                if (_uncommon is not null)
+                    _uncommon._uncoercedBaseValue = value;
+            }
+
+            if (valueChanged)
+            {
+                using var notifying = PropertyNotifying.Start(owner.Owner, property);
+                owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true);
+                if (property.Inherits)
+                    owner.OnInheritedEffectiveValueChanged(property, oldValue, this);
+            }
+            else if (baseValueChanged)
+            {
+                owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false);
+            }
+        }
+
+        private void SetAndRaiseCore(
+            ValueStore owner,
+            StyledPropertyBase<T> property,
+            T value,
+            BindingPriority priority,
+            T baseValue,
+            BindingPriority basePriority)
+        {
+            Debug.Assert(priority < BindingPriority.Inherited);
+            Debug.Assert(basePriority > BindingPriority.Animation);
+            Debug.Assert(priority <= basePriority);
+
+            var oldValue = Value;
+            var valueChanged = false;
+            var baseValueChanged = false;
+            var v = value;
+            var bv = baseValue;
+
+            if (_uncommon?._coerce is { } coerce)
+            {
+                v = coerce(owner.Owner, value);
+                bv = coerce(owner.Owner, baseValue);
+            }
+
+            if (priority != BindingPriority.Unset && !EqualityComparer<T>.Default.Equals(Value, v))
+            {
+                Value = v;
+                valueChanged = true;
+                if (_uncommon is not null)
+                    _uncommon._uncoercedValue = value;
+            }
+
+            if (priority != BindingPriority.Unset &&
+                (BasePriority == BindingPriority.Unset ||
+                 !EqualityComparer<T>.Default.Equals(_baseValue, bv)))
+            {
+                _baseValue = v;
+                baseValueChanged = true;
+                if (_uncommon is not null)
+                    _uncommon._uncoercedValue = baseValue;
+            }
+
+            Priority = priority;
+            BasePriority = basePriority;
+
+            if (valueChanged)
+            {
+                using var notifying = PropertyNotifying.Start(owner.Owner, property);
+                owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true);
+                if (property.Inherits)
+                    owner.OnInheritedEffectiveValueChanged(property, oldValue, this);
+            }
+            
+            if (baseValueChanged)
+            {
+                owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false);
+            }
+        }
+
+        private class UncommonFields
+        {
+            public Func<IAvaloniaObject, T, T>? _coerce;
+            public T? _uncoercedValue;
+            public T? _uncoercedBaseValue;
+        }
+    }
+}

+ 0 - 8
src/Avalonia.Base/PropertyStore/IBatchUpdate.cs

@@ -1,8 +0,0 @@
-namespace Avalonia.PropertyStore
-{
-    internal interface IBatchUpdate
-    {
-        void BeginBatchUpdate();
-        void EndBatchUpdate();
-    }
-}

+ 0 - 18
src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs

@@ -1,18 +0,0 @@
-namespace Avalonia.PropertyStore
-{
-    /// <summary>
-    /// Represents an untyped interface to <see cref="IPriorityValueEntry{T}"/>.
-    /// </summary>
-    internal interface IPriorityValueEntry : IValue
-    {
-    }
-
-    /// <summary>
-    /// Represents an object that can act as an entry in a <see cref="PriorityValue{T}"/>.
-    /// </summary>
-    /// <typeparam name="T">The property type.</typeparam>
-    internal interface IPriorityValueEntry<T> : IPriorityValueEntry, IValue<T>
-    {
-        void Reparent(PriorityValue<T> parent);
-    }
-}

+ 0 - 28
src/Avalonia.Base/PropertyStore/IValue.cs

@@ -1,28 +0,0 @@
-using Avalonia.Data;
-
-namespace Avalonia.PropertyStore
-{
-    /// <summary>
-    /// Represents an untyped interface to <see cref="IValue{T}"/>.
-    /// </summary>
-    internal interface IValue
-    {
-        BindingPriority Priority { get; }
-        Optional<object?> GetValue();
-        void Start();
-        void RaiseValueChanged(
-            AvaloniaObject owner,
-            AvaloniaProperty property,
-            Optional<object?> oldValue,
-            Optional<object?> newValue);
-    }
-
-    /// <summary>
-    /// Represents an object that can act as an entry in a <see cref="ValueStore"/>.
-    /// </summary>
-    /// <typeparam name="T">The property type.</typeparam>
-    internal interface IValue<T> : IValue
-    {
-        Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation);
-    }
-}

+ 30 - 0
src/Avalonia.Base/PropertyStore/IValueEntry.cs

@@ -0,0 +1,30 @@
+using System;
+
+namespace Avalonia.PropertyStore
+{
+    /// <summary>
+    /// Represents an untyped value entry in a <see cref="ValueFrame"/>.
+    /// </summary>
+    internal interface IValueEntry
+    {
+        bool HasValue { get; }
+
+        /// <summary>
+        /// Gets the property that this value applies to.
+        /// </summary>
+        AvaloniaProperty Property { get; }
+
+        /// <summary>
+        /// Gets the value associated with the entry.
+        /// </summary>
+        /// <exception cref="AvaloniaInternalException">
+        /// The entry has no value.
+        /// </exception>
+        object? GetValue();
+
+        /// <summary>
+        /// Called when the value entry is removed from the value store.
+        /// </summary>
+        void Unsubscribe();
+    }
+}

+ 16 - 0
src/Avalonia.Base/PropertyStore/IValueEntry`1.cs

@@ -0,0 +1,16 @@
+namespace Avalonia.PropertyStore
+{
+    /// <summary>
+    /// Represents a typed value entry in a <see cref="ValueFrame"/>.
+    /// </summary>
+    internal interface IValueEntry<T> : IValueEntry
+    {
+        /// <summary>
+        /// Gets the value associated with the entry.
+        /// </summary>
+        /// <exception cref="AvaloniaInternalException">
+        /// The entry has no value.
+        /// </exception>
+        new T GetValue();
+    }
+}

Деякі файли не було показано, через те що забагато файлів було змінено