Browse Source

Merge branch 'master' into feature/DisplayMemberBinding

Max Katz 2 years ago
parent
commit
a05c2a428e
63 changed files with 1026 additions and 375 deletions
  1. 0 0
      .ncrunch/Avalonia.Benchmarks.v3.ncrunchproject
  2. 0 0
      .ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject
  3. 0 0
      .ncrunch/Avalonia.Browser.v3.ncrunchproject
  4. 0 0
      .ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject
  5. 5 0
      .ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject
  6. 5 0
      .ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject
  7. 5 0
      .ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject
  8. 5 0
      .ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject
  9. 5 0
      .ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject
  10. 5 0
      .ncrunch/ControlCatalog.Browser.v3.ncrunchproject
  11. 5 0
      .ncrunch/MobileSandbox.v3.ncrunchproject
  12. 33 18
      samples/ControlCatalog/Pages/DataGridPage.xaml
  13. 4 0
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  14. 25 0
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  15. 21 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  16. 1 1
      src/Avalonia.Base/AvaloniaProperty.cs
  17. 13 9
      src/Avalonia.Base/Data/BindingPriority.cs
  18. 9 2
      src/Avalonia.Base/Layout/Layoutable.cs
  19. 36 0
      src/Avalonia.Base/PropertyStore/FramePriority.cs
  20. 2 2
      src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs
  21. 16 4
      src/Avalonia.Base/PropertyStore/ValueFrame.cs
  22. 54 5
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  23. 115 95
      src/Avalonia.Base/StyledElement.cs
  24. 5 2
      src/Avalonia.Base/Styling/ControlTheme.cs
  25. 0 2
      src/Avalonia.Base/Styling/IStyleable.cs
  26. 3 2
      src/Avalonia.Base/Styling/Style.cs
  27. 2 4
      src/Avalonia.Base/Styling/StyleBase.cs
  28. 10 3
      src/Avalonia.Base/Styling/StyleInstance.cs
  29. 2 1
      src/Avalonia.Base/Styling/Styles.cs
  30. 68 23
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  31. 20 19
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  32. 9 6
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  33. 11 14
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  34. 6 8
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  35. 38 34
      src/Avalonia.Controls.DataGrid/DataGridRows.cs
  36. 1 6
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  37. 0 5
      src/Avalonia.Controls.DataGrid/Themes/Simple.xaml
  38. 1 1
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  39. 20 39
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  40. 1 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs
  41. 1 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  42. 1 1
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  43. 2 1
      tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs
  44. 1 1
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs
  45. 1 1
      tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs
  46. 2 1
      tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
  47. 54 26
      tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs
  48. 207 11
      tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
  49. 1 1
      tests/Avalonia.Benchmarks/Base/StyledPropertyBenchmark.cs
  50. 149 0
      tests/Avalonia.Benchmarks/Styling/ControlTheme_Change.cs
  51. 3 2
      tests/Avalonia.Benchmarks/Styling/Style_Activation.cs
  52. 3 1
      tests/Avalonia.Benchmarks/Styling/Style_Apply.cs
  53. 1 1
      tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs
  54. 6 4
      tests/Avalonia.Benchmarks/Styling/Style_ClassSelector.cs
  55. 3 1
      tests/Avalonia.Benchmarks/Styling/Style_NonActive.cs
  56. 2 2
      tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
  57. 2 2
      tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
  58. 2 2
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  59. 1 1
      tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
  60. 1 1
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs
  61. 2 1
      tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs
  62. 6 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs
  63. 14 0
      tests/Avalonia.UnitTests/StyleHelpers.cs

+ 0 - 0
.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject → .ncrunch/Avalonia.Benchmarks.v3.ncrunchproject


+ 0 - 0
.ncrunch/Avalonia.Web.v3.ncrunchproject → .ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject


+ 0 - 0
.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject → .ncrunch/Avalonia.Browser.v3.ncrunchproject


+ 0 - 0
.ncrunch/ControlCatalog.Web.v3.ncrunchproject → .ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject


+ 5 - 0
.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <InstrumentOutputAssembly>False</InstrumentOutputAssembly>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <InstrumentOutputAssembly>False</InstrumentOutputAssembly>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <InstrumentOutputAssembly>False</InstrumentOutputAssembly>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <InstrumentOutputAssembly>False</InstrumentOutputAssembly>
+  </Settings>
+</ProjectConfiguration>

+ 5 - 0
.ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject

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

+ 5 - 0
.ncrunch/ControlCatalog.Browser.v3.ncrunchproject

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

+ 5 - 0
.ncrunch/MobileSandbox.v3.ncrunchproject

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

+ 33 - 18
samples/ControlCatalog/Pages/DataGridPage.xaml

@@ -14,22 +14,6 @@
       <Setter Property="Background" Value="{Binding Path=GDP, Mode=OneWay, Converter={StaticResource GDPConverter}}" />
     </ControlTheme>
   </UserControl.Resources>
-  <UserControl.Styles>
-    <Style Selector="DataGridColumnHeader:nth-last-child(1)">
-      <Setter Property="FontWeight" Value="Bold" />
-    </Style>
-    <Style Selector="DataGridCell:nth-last-child(1)">
-      <Setter Property="FontWeight" Value="Bold" />
-    </Style>
-    <Style Selector="DataGrid#dataGridGrouping DataGridRow:nth-child(5n+3)">
-      <Setter Property="Foreground" Value="Red" />
-      <Setter Property="FontWeight" Value="Bold" />
-    </Style>
-    <Style Selector="DataGrid#dataGridGrouping DataGridRow:nth-last-child(5n+1)">
-      <Setter Property="Foreground" Value="Blue" />
-      <Setter Property="FontWeight" Value="Bold" />
-    </Style>
-  </UserControl.Styles>
   <Grid RowDefinitions="Auto,Auto,*">
     <StackPanel Orientation="Vertical" Spacing="4" Grid.Row="0">
       <TextBlock Classes="h2">A control for displaying and interacting with a data source.</TextBlock>
@@ -45,8 +29,7 @@
           <CheckBox x:Name="ShowGDP"  IsChecked="True"  Content="Toggle GDP Column Visibility"
                     DockPanel.Dock="Top"/>
           <DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All"
-                    RowBackground="#1000"
-                    AlternatingRowBackground="#1fff">
+                    RowBackground="#1000">
             <DataGrid.Columns>
               <!-- Using HeaderTemplate -->
               <DataGridTextColumn Header="Country" HeaderTemplate="{StaticResource Demo.DataTemplates.CountryHeader}" Binding="{Binding Name}"  Width="6*" x:DataType="local:Country" />
@@ -59,6 +42,24 @@
                                   IsVisible="{Binding #ShowGDP.IsChecked}"
                                   x:DataType="local:Country" />
             </DataGrid.Columns>
+            <DataGrid.CellTheme>
+              <ControlTheme TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
+                <ControlTheme.Children>
+                  <Style Selector="^:nth-child(1)">
+                    <Setter Property="FontWeight" Value="Bold" />
+                  </Style>
+                </ControlTheme.Children>
+              </ControlTheme>
+            </DataGrid.CellTheme>
+            <DataGrid.ColumnHeaderTheme>
+              <ControlTheme TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
+                <ControlTheme.Children>
+                  <Style Selector="^:nth-child(1)">
+                    <Setter Property="FontWeight" Value="Bold" />
+                  </Style>
+                </ControlTheme.Children>
+              </ControlTheme>
+            </DataGrid.ColumnHeaderTheme>
           </DataGrid>
         </DockPanel>
       </TabItem>
@@ -71,6 +72,20 @@
             <DataGridTextColumn DisplayIndex="2" Header="Area" Binding="{Binding Area}" Width="3*" x:DataType="local:Country" />
             <DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*" x:DataType="local:Country" />
           </DataGrid.Columns>
+          <DataGrid.RowTheme>
+            <ControlTheme TargetType="DataGridRow" BasedOn="{StaticResource {x:Type DataGridRow}}">
+              <ControlTheme.Children>
+                <Style Selector="^:nth-child(5n+3)">
+                  <Setter Property="Foreground" Value="Red" />
+                  <Setter Property="FontWeight" Value="Bold" />
+                </Style>
+                <Style Selector="^:nth-last-child(5n+1)">
+                  <Setter Property="Foreground" Value="Blue" />
+                  <Setter Property="FontWeight" Value="Bold" />
+                </Style>
+              </ControlTheme.Children>
+            </ControlTheme>
+          </DataGrid.RowTheme>
         </DataGrid>
       </TabItem>
       <TabItem x:Name="EditableTab" Header="Editable">

+ 4 - 0
src/Android/Avalonia.Android/AndroidInputMethod.cs

@@ -95,6 +95,10 @@ namespace Avalonia.Android
 
                 _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
             }
+            else
+            {
+                _imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.ImplicitOnly);
+            }
         }
 
         private void SurroundingTextChanged(object sender, EventArgs e)

+ 25 - 0
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@@ -4,9 +4,12 @@ using Android.Content;
 using Android.Content.Res;
 using Android.OS;
 using Android.Runtime;
+using Android.Views;
 using AndroidX.AppCompat.App;
 using AndroidX.Lifecycle;
 
+using AndroidRect = Android.Graphics.Rect;
+
 namespace Avalonia.Android
 {
     public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler
@@ -15,6 +18,7 @@ namespace Avalonia.Android
 
         public Action<int, Result, Intent> ActivityResult { get; set; }
         internal AvaloniaView View;
+        private GlobalLayoutListener _listener;
 
         protected override void OnCreate(Bundle savedInstanceState)
         {
@@ -32,6 +36,10 @@ namespace Avalonia.Android
             base.OnCreate(savedInstanceState);
 
             SetContentView(View);
+
+            _listener = new GlobalLayoutListener(View);
+
+            View.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener);
         }
 
         public object Content
@@ -57,6 +65,8 @@ namespace Avalonia.Android
         {
             View.Content = null;
 
+            View.ViewTreeObserver?.RemoveOnGlobalLayoutListener(_listener);
+
             base.OnDestroy();
         }
 
@@ -66,5 +76,20 @@ namespace Avalonia.Android
 
             ActivityResult?.Invoke(requestCode, resultCode, data);
         }
+
+        class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
+        {
+            private AvaloniaView _view;
+
+            public GlobalLayoutListener(AvaloniaView view)
+            {
+                _view = view;
+            }
+
+            public void OnGlobalLayout()
+            {
+                _view.TopLevelImpl?.Resize(_view.TopLevelImpl.ClientSize);
+            }
+        }
     }
 }

+ 21 - 1
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -26,6 +26,7 @@ using Avalonia.Rendering;
 using Avalonia.Rendering.Composition;
 using Java.Lang;
 using Math = System.Math;
+using AndroidRect = Android.Graphics.Rect;
 
 namespace Avalonia.Android.Platform.SkiaPlatform
 {
@@ -63,7 +64,21 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
         public IInputRoot InputRoot { get; private set; }
 
-        public virtual Size ClientSize => Size.ToSize(RenderScaling);
+        public virtual Size ClientSize
+        {
+            get
+            {
+                AndroidRect rect = new AndroidRect();
+                AndroidRect intersection = new AndroidRect();
+
+                _view.GetWindowVisibleDisplayFrame(intersection);
+                _view.GetGlobalVisibleRect(rect);
+
+                intersection.Intersect(rect);
+
+                return new PixelSize(intersection.Right - intersection.Left, intersection.Bottom - intersection.Top).ToSize(RenderScaling);
+            }
+        }
 
         public Size? FrameSize => null;
 
@@ -149,6 +164,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             Resized?.Invoke(size, PlatformResizeReason.Unspecified);
         }
 
+        internal void Resize(Size size)
+        {
+            Resized?.Invoke(size, PlatformResizeReason.Layout);
+        }
+
         class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback, IInitEditorInfo
         {
             private readonly TopLevelImpl _tl;

+ 1 - 1
src/Avalonia.Base/AvaloniaProperty.cs

@@ -177,7 +177,7 @@ namespace Avalonia
         {
             return new IndexerDescriptor
             {
-                Priority = BindingPriority.TemplatedParent,
+                Priority = BindingPriority.Template,
                 Property = property,
             };
         }

+ 13 - 9
src/Avalonia.Base/Data/BindingPriority.cs

@@ -1,7 +1,9 @@
+using System;
+
 namespace Avalonia.Data
 {
     /// <summary>
-    /// The priority of a binding.
+    /// The priority of a value or binding.
     /// </summary>
     public enum BindingPriority
     {
@@ -16,23 +18,22 @@ namespace Avalonia.Data
         LocalValue = 0,
 
         /// <summary>
-        /// A triggered style binding.
+        /// A triggered style value.
         /// </summary>
         /// <remarks>
         /// A style trigger is a selector such as .class which overrides a
-        /// <see cref="TemplatedParent"/> binding. In this way, a basic control can have
-        /// for example a Background from the templated parent which changes when the
-        /// control has the :pointerover class.
+        /// <see cref="Template"/> value. In this way, a control can have, e.g. a Background from
+        /// the template which changes when the control has the :pointerover class.
         /// </remarks>
         StyleTrigger,
 
         /// <summary>
-        /// A binding to a property on the templated parent.
+        /// A value from the control's template.
         /// </summary>
-        TemplatedParent,
+        Template,
 
         /// <summary>
-        /// A style binding.
+        /// A style value.
         /// </summary>
         Style,
         
@@ -42,8 +43,11 @@ namespace Avalonia.Data
         Inherited,
 
         /// <summary>
-        /// The binding is uninitialized.
+        /// The value is uninitialized.
         /// </summary>
         Unset = int.MaxValue,
+
+        [Obsolete("Use Template priority")]
+        TemplatedParent = Template,
     }
 }

+ 9 - 2
src/Avalonia.Base/Layout/Layoutable.cs

@@ -1,5 +1,6 @@
 using System;
 using Avalonia.Logging;
+using Avalonia.Styling;
 using Avalonia.VisualTree;
 
 #nullable enable
@@ -719,9 +720,9 @@ namespace Avalonia.Layout
             return finalSize;
         }
 
-        protected sealed override void InvalidateStyles()
+        internal sealed override void InvalidateStyles(bool recurse)
         {
-            base.InvalidateStyles();
+            base.InvalidateStyles(recurse);
             InvalidateMeasure();
         }
 
@@ -795,6 +796,12 @@ namespace Avalonia.Layout
             base.OnVisualParentChanged(oldParent, newParent);
         }
 
+        private protected override void OnControlThemeChanged()
+        {
+            base.OnControlThemeChanged();
+            InvalidateMeasure();
+        }
+
         /// <summary>
         /// Called when the layout manager raises a LayoutUpdated event.
         /// </summary>

+ 36 - 0
src/Avalonia.Base/PropertyStore/FramePriority.cs

@@ -0,0 +1,36 @@
+using System.Diagnostics;
+using Avalonia.Data;
+
+namespace Avalonia.PropertyStore
+{
+    internal enum FramePriority : sbyte
+    {
+        Animation,
+        AnimationTemplatedParentTheme,
+        AnimationTheme,
+        StyleTrigger,
+        StyleTriggerTemplatedParentTheme,
+        StyleTriggerTheme,
+        Template,
+        TemplateTemplatedParentTheme,
+        TemplateTheme,
+        Style,
+        StyleTemplatedParentTheme,
+        StyleTheme,
+    }
+
+    internal static class FramePriorityExtensions
+    {
+        public static FramePriority ToFramePriority(this BindingPriority priority, FrameType type = FrameType.Style)
+        {
+            Debug.Assert(priority != BindingPriority.LocalValue);
+            var p = (int)(priority > 0 ? priority : priority + 1);
+            return (FramePriority)(p * 3 + (int)type);
+        }
+
+        public static bool IsType(this FramePriority priority, FrameType type)
+        {
+            return (FrameType)((int)priority % 3) == type;
+        }
+    }
+}

+ 2 - 2
src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs

@@ -7,11 +7,11 @@ namespace Avalonia.PropertyStore
     /// Holds values in a <see cref="ValueStore"/> set by one of the SetValue or AddBinding
     /// overloads with non-LocalValue priority.
     /// </summary>
-    internal class ImmediateValueFrame : ValueFrame
+    internal sealed class ImmediateValueFrame : ValueFrame
     {
         public ImmediateValueFrame(BindingPriority priority)
+            : base(priority, FrameType.Style)
         {
-            Priority = priority;
         }
 
         public TypedBindingEntry<T> AddBinding<T>(

+ 16 - 4
src/Avalonia.Base/PropertyStore/ValueFrame.cs

@@ -1,13 +1,18 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using Avalonia.Data;
 using Avalonia.Utilities;
-using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
 
 namespace Avalonia.PropertyStore
 {
+    internal enum FrameType
+    {
+        Style,
+        TemplatedParentTheme,
+        Theme,
+    }
+
     internal abstract class ValueFrame
     {
         private List<IValueEntry>? _entries;
@@ -15,11 +20,18 @@ namespace Avalonia.PropertyStore
         private ValueStore? _owner;
         private bool _isShared;
 
+        protected ValueFrame(BindingPriority priority, FrameType type)
+        {
+            Priority = priority;
+            FramePriority = priority.ToFramePriority(type);
+        }
+
         public int EntryCount => _index.Count;
         public bool IsActive => GetIsActive(out _);
         public ValueStore? Owner => !_isShared ? _owner : 
             throw new AvaloniaInternalException("Cannot get owner for shared ValueFrame");
-        public BindingPriority Priority { get; protected set; }
+        public BindingPriority Priority { get; }
+        public FramePriority FramePriority { get; }
 
         public bool Contains(AvaloniaProperty property) => _index.ContainsKey(property);
 

+ 54 - 5
src/Avalonia.Base/PropertyStore/ValueStore.cs

@@ -2,10 +2,12 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
+using System.Linq;
 using Avalonia.Data;
 using Avalonia.Diagnostics;
-using Avalonia.Logging;
+using Avalonia.Styling;
 using Avalonia.Utilities;
+using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
 
 namespace Avalonia.PropertyStore
 {
@@ -580,6 +582,53 @@ namespace Avalonia.PropertyStore
             return false;
         }
 
+        public void RemoveFrames(FrameType type)
+        {
+            var removed = false;
+
+            for (var i = _frames.Count - 1; i >= 0; --i)
+            {
+                var frame = _frames[i];
+
+                if (frame is not ImmediateValueFrame && frame.FramePriority.IsType(type))
+                {
+                    _frames.RemoveAt(i);
+                    frame.Dispose();
+                    removed = true;
+                }
+            }
+
+            if (removed)
+            {
+                ++_frameGeneration;
+                ReevaluateEffectiveValues();
+            }
+        }
+
+
+        public void RemoveFrames(IReadOnlyList<IStyle> styles)
+        {
+            var removed = false;
+
+            for (var i = _frames.Count - 1; i >= 0; --i)
+            {
+                var frame = _frames[i];
+
+                if (frame is StyleInstance style && styles.Contains(style.Source))
+                {
+                    _frames.RemoveAt(i);
+                    frame.Dispose();
+                    removed = true;
+                }
+            }
+
+            if (removed)
+            {
+                ++_frameGeneration;
+                ReevaluateEffectiveValues();
+            }
+        }
+
         public AvaloniaPropertyValue GetDiagnostic(AvaloniaProperty property)
         {
             object? value;
@@ -612,7 +661,7 @@ namespace Avalonia.PropertyStore
         {
             Debug.Assert(!_frames.Contains(frame));
 
-            var index = BinarySearchFrame(frame.Priority);
+            var index = BinarySearchFrame(frame.FramePriority);
             _frames.Insert(index, frame);
             ++_frameGeneration;
             frame.SetOwner(this);
@@ -626,7 +675,7 @@ namespace Avalonia.PropertyStore
         {
             Debug.Assert(priority != BindingPriority.LocalValue);
 
-            var index = BinarySearchFrame(priority);
+            var index = BinarySearchFrame(priority.ToFramePriority());
 
             if (index > 0 && _frames[index - 1] is ImmediateValueFrame f &&
                 f.Priority == priority &&
@@ -914,7 +963,7 @@ namespace Avalonia.PropertyStore
             }
         }
 
-        private int BinarySearchFrame(BindingPriority priority)
+        private int BinarySearchFrame(FramePriority priority)
         {
             var lo = 0;
             var hi = _frames.Count - 1;
@@ -923,7 +972,7 @@ namespace Avalonia.PropertyStore
             while (lo <= hi)
             {
                 var i = lo + ((hi - lo) >> 1);
-                var order = priority - _frames[i].Priority;
+                var order = priority - _frames[i].FramePriority;
 
                 if (order <= 0)
                 {

+ 115 - 95
src/Avalonia.Base/StyledElement.cs

@@ -3,6 +3,7 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.ComponentModel;
+using System.Diagnostics;
 using System.Linq;
 using Avalonia.Animation;
 using Avalonia.Collections;
@@ -11,6 +12,7 @@ using Avalonia.Data;
 using Avalonia.Diagnostics;
 using Avalonia.Logging;
 using Avalonia.LogicalTree;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
 
 namespace Avalonia
@@ -69,10 +71,10 @@ namespace Avalonia
         private IAvaloniaList<ILogical>? _logicalChildren;
         private IResourceDictionary? _resources;
         private Styles? _styles;
-        private bool _styled;
+        private bool _stylesApplied;
+        private bool _themeApplied;
         private ITemplatedControl? _templatedParent;
         private bool _dataContextUpdating;
-        private bool _hasPromotedTheme;
         private ControlTheme? _implicitTheme;
 
         /// <summary>
@@ -141,7 +143,7 @@ namespace Avalonia
 
             set
             {
-                if (_styled)
+                if (_stylesApplied)
                 {
                     throw new InvalidOperationException("Cannot set Name : styled element already styled.");
                 }
@@ -353,38 +355,33 @@ namespace Avalonia
         /// </returns>
         public bool ApplyStyling()
         {
-            if (_initCount == 0 && !_styled)
+            if (_initCount == 0 && (!_stylesApplied || !_themeApplied))
             {
-                var hasPromotedTheme = _hasPromotedTheme;
-
                 GetValueStore().BeginStyling();
 
                 try
                 {
-                    ApplyControlTheme();
-                    ApplyStyles(this);
+                    if (!_themeApplied)
+                    {
+                        ApplyControlTheme();
+                        _themeApplied = true;
+                    }
+
+                    if (!_stylesApplied)
+                    {
+                        ApplyStyles(this);
+                        _stylesApplied = true;
+                    }
                 }
                 finally
                 {
-                    _styled = true;
                     GetValueStore().EndStyling();
                 }
-
-                if (hasPromotedTheme)
-                {
-                    _hasPromotedTheme = false;
-                    ClearValue(ThemeProperty);
-                }
             }
 
-            return _styled;
+            return _stylesApplied;
         }
 
-        /// <summary>
-        /// Detaches all styles from the element and queues a restyle.
-        /// </summary>
-        protected virtual void InvalidateStyles() => DetachStyles();
-
         protected void InitializeIfNeeded()
         {
             if (_initCount == 0 && !IsInitialized)
@@ -506,17 +503,16 @@ namespace Avalonia
             };
         }
 
-        void IStyleable.DetachStyles() => DetachStyles();
-
         void IStyleHost.StylesAdded(IReadOnlyList<IStyle> styles)
         {
-            InvalidateStylesOnThisAndDescendents();
+            if (HasSettersOrAnimations(styles))
+                InvalidateStyles(recurse: true);
         }
 
         void IStyleHost.StylesRemoved(IReadOnlyList<IStyle> styles)
         {
-            var allStyles = RecurseStyles(styles);
-            DetachStylesFromThisAndDescendents(allStyles);
+            if (FlattenStyles(styles) is { } allStyles)
+                DetachStyles(allStyles);
         }
 
         protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
@@ -615,31 +611,25 @@ namespace Avalonia
 
             if (change.Property == ThemeProperty)
             {
-                var (oldValue, newValue) = change.GetOldAndNewValue<ControlTheme?>();
-
-                // Changing the theme detaches all styles, meaning that if the theme property was
-                // set via a style, it will get cleared! To work around this, if the value was
-                // applied at less than local value priority then promote the value to local value
-                // priority until styling is re-applied.
-                if (change.Priority > BindingPriority.LocalValue)
-                {
-                    Theme = newValue;
-                    _hasPromotedTheme = true;
-                }
-                else if (_hasPromotedTheme && change.Priority == BindingPriority.LocalValue)
-                {
-                    _hasPromotedTheme = false;
-                }
-
-                InvalidateStyles();
-
-                if (oldValue is not null)
-                    DetachControlThemeFromTemplateChildren(oldValue);
+                OnControlThemeChanged();
+                _themeApplied = false;
             }
         }
 
-        internal virtual void DetachControlThemeFromTemplateChildren(ControlTheme theme)
+        private protected virtual void OnControlThemeChanged()
         {
+            var values = GetValueStore();
+            values.BeginStyling();
+            try { values.RemoveFrames(FrameType.Theme); }
+            finally { values.EndStyling(); }
+        }
+
+        internal virtual void OnTemplatedParentControlThemeChanged()
+        {
+            var values = GetValueStore();
+            values.BeginStyling();
+            try { values.RemoveFrames(FrameType.TemplatedParentTheme); }
+            finally { values.EndStyling(); }
         }
 
         internal ControlTheme? GetEffectiveTheme()
@@ -667,6 +657,23 @@ namespace Avalonia
             return null;
         }
 
+        internal virtual void InvalidateStyles(bool recurse)
+        {
+            var values = GetValueStore();
+            values.BeginStyling();
+            try { values.RemoveFrames(FrameType.Style); }
+            finally { values.EndStyling(); }
+
+            _stylesApplied = false;
+
+            if (recurse && GetInheritanceChildren() is { } children)
+            {
+                var childCount = children.Count;
+                for (var i = 0; i < childCount; ++i)
+                    (children[i] as StyledElement)?.InvalidateStyles(recurse);
+            }
+        }
+
         private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted)
         {
             if (o is StyledElement element)
@@ -736,26 +743,28 @@ namespace Avalonia
             var theme = GetEffectiveTheme();
 
             if (theme is not null)
-                ApplyControlTheme(theme);
+                ApplyControlTheme(theme, FrameType.Theme);
 
             if (TemplatedParent is StyledElement styleableParent &&
                 styleableParent.GetEffectiveTheme() is { } parentTheme)
             {
-                ApplyControlTheme(parentTheme);
+                ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme);
             }
         }
 
-        private void ApplyControlTheme(ControlTheme theme)
+        private void ApplyControlTheme(ControlTheme theme, FrameType type)
         {
+            Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme);
+
             if (theme.BasedOn is ControlTheme basedOn)
-                ApplyControlTheme(basedOn);
+                ApplyControlTheme(basedOn, type);
 
-            theme.TryAttach(this, null);
+            theme.TryAttach(this, type);
 
             if (theme.HasChildren)
             {
                 foreach (var child in theme.Children)
-                    ApplyStyle(child, null);
+                    ApplyStyle(child, null, type);
             }
         }
 
@@ -768,17 +777,17 @@ namespace Avalonia
             if (host.IsStylesInitialized)
             {
                 foreach (var style in host.Styles)
-                    ApplyStyle(style, host);
+                    ApplyStyle(style, host, FrameType.Style);
             }
         }
 
-        private void ApplyStyle(IStyle style, IStyleHost? host)
+        private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type)
         {
             if (style is Style s)
-                s.TryAttach(this, host);
+                s.TryAttach(this, host, type);
 
             foreach (var child in style.Children)
-                ApplyStyle(child, host);
+                ApplyStyle(child, host, type);
         }
 
         private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
@@ -824,7 +833,7 @@ namespace Avalonia
             {
                 _logicalRoot = null;
                 _implicitTheme = null;
-                DetachStyles();
+                InvalidateStyles(recurse: false);
                 OnDetachedFromLogicalTree(e);
                 DetachedFromLogicalTree?.Invoke(this, e);
 
@@ -886,70 +895,81 @@ namespace Avalonia
             }
         }
 
-        private void DetachStyles(IReadOnlyList<StyleBase>? styles = null)
+        private void DetachStyles(IReadOnlyList<Style> styles)
         {
-            var valueStore = GetValueStore();
+            var values = GetValueStore();
+            values.BeginStyling();
+            try { values.RemoveFrames(styles); }
+            finally { values.EndStyling(); }
 
-            valueStore.BeginStyling();
-
-            for (var i = valueStore.Frames.Count - 1; i >= 0; --i)
+            if (_logicalChildren is not null)
             {
-                if (valueStore.Frames[i] is StyleInstance si &&
-                    (styles is null || styles.Contains(si.Source)))
+                var childCount = _logicalChildren.Count;
+
+                for (var i = 0; i < childCount; ++i)
                 {
-                    valueStore.RemoveFrame(si);
+                    (_logicalChildren[i] as StyledElement)?.DetachStyles(styles);
                 }
             }
-
-            valueStore.EndStyling();
-            _styled = false;
         }
 
-        private void InvalidateStylesOnThisAndDescendents()
+        private void NotifyResourcesChanged(
+            ResourcesChangedEventArgs? e = null,
+            bool propagate = true)
         {
-            InvalidateStyles();
-
-            if (_logicalChildren is object)
+            if (ResourcesChanged is object)
             {
-                var childCount = _logicalChildren.Count;
+                e ??= ResourcesChangedEventArgs.Empty;
+                ResourcesChanged(this, e);
+            }
 
-                for (var i = 0; i < childCount; ++i)
-                {
-                    (_logicalChildren[i] as StyledElement)?.InvalidateStylesOnThisAndDescendents();
-                }
+            if (propagate)
+            {
+                e ??= ResourcesChangedEventArgs.Empty;
+                NotifyChildResourcesChanged(e);
             }
         }
 
-        private void DetachStylesFromThisAndDescendents(IReadOnlyList<StyleBase> styles)
+        private static IReadOnlyList<Style>? FlattenStyles(IReadOnlyList<IStyle> styles)
         {
-            DetachStyles(styles);
+            List<Style>? result = null;
 
-            if (_logicalChildren is object)
+            static void FlattenStyle(IStyle style, ref List<Style>? result)
             {
-                var childCount = _logicalChildren.Count;
+                if (style is Style s)
+                    (result ??= new()).Add(s);
+                FlattenStyles(style.Children, ref result);
+            }
 
-                for (var i = 0; i < childCount; ++i)
-                {
-                    (_logicalChildren[i] as StyledElement)?.DetachStylesFromThisAndDescendents(styles);
-                }
+            static void FlattenStyles(IReadOnlyList<IStyle> styles, ref List<Style>? result)
+            {
+                var count = styles.Count;
+                for (var i = 0; i < count; ++i)
+                    FlattenStyle(styles[i], ref result);
             }
+
+            FlattenStyles(styles, ref result);
+            return result;
         }
 
-        private void NotifyResourcesChanged(
-            ResourcesChangedEventArgs? e = null,
-            bool propagate = true)
+        private static bool HasSettersOrAnimations(IReadOnlyList<IStyle> styles)
         {
-            if (ResourcesChanged is object)
+            static bool StyleHasSettersOrAnimations(IStyle style)
             {
-                e ??= ResourcesChangedEventArgs.Empty;
-                ResourcesChanged(this, e);
+                if (style is StyleBase s && s.HasSettersOrAnimations)
+                    return true;
+                return HasSettersOrAnimations(style.Children);
             }
 
-            if (propagate)
+            var count = styles.Count;
+
+            for (var i = 0; i < count; ++i)
             {
-                e ??= ResourcesChangedEventArgs.Empty;
-                NotifyChildResourcesChanged(e);
+                if (StyleHasSettersOrAnimations(styles[i]))
+                    return true;
             }
+
+            return false;
         }
 
         private static IReadOnlyList<StyleBase> RecurseStyles(IReadOnlyList<IStyle> styles)

+ 5 - 2
src/Avalonia.Base/Styling/ControlTheme.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics;
 using Avalonia.PropertyStore;
 
 namespace Avalonia.Styling
@@ -36,8 +37,10 @@ namespace Avalonia.Styling
             throw new InvalidOperationException("ControlThemes cannot be added as a nested style.");
         }
 
-        internal override SelectorMatchResult TryAttach(IStyleable target, object? host)
+        internal SelectorMatchResult TryAttach(IStyleable target, FrameType type)
         {
+            Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme);
+
             _ = target ?? throw new ArgumentNullException(nameof(target));
 
             if (TargetType is null)
@@ -45,7 +48,7 @@ namespace Avalonia.Styling
 
             if (HasSettersOrAnimations && TargetType.IsAssignableFrom(target.StyleKey))
             {
-                Attach(target, null);
+                Attach(target, null, type);
                 return SelectorMatchResult.AlwaysThisType;
             }
 

+ 0 - 2
src/Avalonia.Base/Styling/IStyleable.cs

@@ -24,7 +24,5 @@ namespace Avalonia.Styling
         /// Gets the template parent of this element if the control comes from a template.
         /// </summary>
         ITemplatedControl? TemplatedParent { get; }
-
-        void DetachStyles();
     }
 }

+ 3 - 2
src/Avalonia.Base/Styling/Style.cs

@@ -1,4 +1,5 @@
 using System;
+using Avalonia.PropertyStore;
 
 namespace Avalonia.Styling
 {
@@ -58,7 +59,7 @@ namespace Avalonia.Styling
             base.SetParent(parent);
         }
 
-        internal override SelectorMatchResult TryAttach(IStyleable target, object? host)
+        internal SelectorMatchResult TryAttach(IStyleable target, object? host, FrameType type)
         {
             _ = target ?? throw new ArgumentNullException(nameof(target));
 
@@ -73,7 +74,7 @@ namespace Avalonia.Styling
 
                 if (match.IsMatch)
                 {
-                    Attach(target, match.Activator);
+                    Attach(target, match.Activator, type);
                 }
 
                 result = match.Result;

+ 2 - 4
src/Avalonia.Base/Styling/StyleBase.cs

@@ -92,9 +92,7 @@ namespace Avalonia.Styling
             return false;
         }
 
-        internal abstract SelectorMatchResult TryAttach(IStyleable target, object? host);
-
-        internal ValueFrame Attach(IStyleable target, IStyleActivator? activator)
+        internal ValueFrame Attach(IStyleable target, IStyleActivator? activator, FrameType type)
         {
             if (target is not AvaloniaObject ao)
                 throw new InvalidOperationException("Styles can only be applied to AvaloniaObjects.");
@@ -109,7 +107,7 @@ namespace Avalonia.Styling
             {
                 var canShareInstance = activator is null;
 
-                instance = new StyleInstance(this, activator);
+                instance = new StyleInstance(this, activator, type);
 
                 if (_setters is not null)
                 {

+ 10 - 3
src/Avalonia.Base/Styling/StyleInstance.cs

@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Reactive.Subjects;
 using Avalonia.Animation;
 using Avalonia.Data;
@@ -27,10 +26,13 @@ namespace Avalonia.Styling
         private List<IAnimation>? _animations;
         private Subject<bool>? _animationTrigger;
 
-        public StyleInstance(IStyle style, IStyleActivator? activator)
+        public StyleInstance(
+            IStyle style,
+            IStyleActivator? activator,
+            FrameType type)
+            : base(GetPriority(activator), type)
         {
             _activator = activator;
-            Priority = activator is object ? BindingPriority.StyleTrigger : BindingPriority.Style;
             Source = style;
         }
 
@@ -99,5 +101,10 @@ namespace Avalonia.Styling
             hasChanged = _isActive != previous;
             return _isActive;
         }
+
+        private static BindingPriority GetPriority(IStyleActivator? activator)
+        {
+            return activator is not null ? BindingPriority.StyleTrigger : BindingPriority.Style;
+        }
     }
 }

+ 2 - 1
src/Avalonia.Base/Styling/Styles.cs

@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Collections.Specialized;
 using Avalonia.Collections;
 using Avalonia.Controls;
+using Avalonia.PropertyStore;
 
 namespace Avalonia.Styling
 {
@@ -233,7 +234,7 @@ namespace Avalonia.Styling
             {
                 if (s is not Style style)
                     continue;
-                var r = style.TryAttach(target, host);
+                var r = style.TryAttach(target, host, FrameType.Style);
                 if (r > result)
                     result = r;
             }

+ 68 - 23
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -26,6 +26,7 @@ using Avalonia.Controls.Utils;
 using Avalonia.Layout;
 using Avalonia.Controls.Metadata;
 using Avalonia.Input.GestureRecognizers;
+using Avalonia.Styling;
 
 namespace Avalonia.Controls
 {
@@ -238,29 +239,72 @@ namespace Avalonia.Controls
             AvaloniaProperty.Register<DataGrid, DataGridLength>(nameof(ColumnWidth), defaultValue: DataGridLength.Auto);
 
         /// <summary>
-        /// Gets or sets the standard width or automatic sizing mode of columns in the control.
+        /// Identifies the <see cref="RowTheme"/> dependency property.
         /// </summary>
-        public DataGridLength ColumnWidth
+        public static readonly StyledProperty<ControlTheme> RowThemeProperty =
+            AvaloniaProperty.Register<DataGrid, ControlTheme>(nameof(RowTheme));
+
+        /// <summary>
+        /// Gets or sets the theme applied to all rows.
+        /// </summary>
+        public ControlTheme RowTheme
         {
-            get { return GetValue(ColumnWidthProperty); }
-            set { SetValue(ColumnWidthProperty, value); }
+            get { return GetValue(RowThemeProperty); }
+            set { SetValue(RowThemeProperty, value); }
         }
 
-        public static readonly StyledProperty<IBrush> AlternatingRowBackgroundProperty =
-            AvaloniaProperty.Register<DataGrid, IBrush>(nameof(AlternatingRowBackground));
+        /// <summary>
+        /// Identifies the <see cref="CellTheme"/> dependency property.
+        /// </summary>
+        public static readonly StyledProperty<ControlTheme> CellThemeProperty =
+            AvaloniaProperty.Register<DataGrid, ControlTheme>(nameof(CellTheme));
 
         /// <summary>
-        /// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint the background of odd-numbered rows.
+        /// Gets or sets the theme applied to all cells.
         /// </summary>
-        /// <returns>
-        /// The brush that is used to paint the background of odd-numbered rows. The default is a
-        /// <see cref="T:System.Windows.Media.SolidColorBrush" /> with a
-        /// <see cref="P:System.Windows.Media.SolidColorBrush.Color" /> value of white (ARGB value #00FFFFFF).
-        /// </returns>
-        public IBrush AlternatingRowBackground
+        public ControlTheme CellTheme
+        {
+            get { return GetValue(CellThemeProperty); }
+            set { SetValue(CellThemeProperty, value); }
+        }
+
+        /// <summary>
+        /// Identifies the <see cref="ColumnHeaderTheme"/> dependency property.
+        /// </summary>
+        public static readonly StyledProperty<ControlTheme> ColumnHeaderThemeProperty =
+            AvaloniaProperty.Register<DataGrid, ControlTheme>(nameof(ColumnHeaderTheme));
+
+        /// <summary>
+        /// Gets or sets the theme applied to all column headers.
+        /// </summary>
+        public ControlTheme ColumnHeaderTheme
+        {
+            get { return GetValue(ColumnHeaderThemeProperty); }
+            set { SetValue(ColumnHeaderThemeProperty, value); }
+        }
+
+        /// <summary>
+        /// Identifies the <see cref="RowGroupTheme"/> dependency property.
+        /// </summary>
+        public static readonly StyledProperty<ControlTheme> RowGroupThemeProperty =
+            AvaloniaProperty.Register<DataGrid, ControlTheme>(nameof(RowGroupTheme));
+
+        /// <summary>
+        /// Gets or sets the theme applied to all row groups.
+        /// </summary>
+        public ControlTheme RowGroupTheme
         {
-            get { return GetValue(AlternatingRowBackgroundProperty); }
-            set { SetValue(AlternatingRowBackgroundProperty, value); }
+            get { return GetValue(RowGroupThemeProperty); }
+            set { SetValue(RowGroupThemeProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the standard width or automatic sizing mode of columns in the control.
+        /// </summary>
+        public DataGridLength ColumnWidth
+        {
+            get { return GetValue(ColumnWidthProperty); }
+            set { SetValue(ColumnWidthProperty, value); }
         }
 
         public static readonly StyledProperty<int> FrozenColumnCountProperty =
@@ -1252,7 +1296,7 @@ namespace Avalonia.Controls
         public event EventHandler<SelectionChangedEventArgs> SelectionChanged
         {
             add { AddHandler(SelectionChangedEvent, value); }
-            remove { AddHandler(SelectionChangedEvent, value); }
+            remove { RemoveHandler(SelectionChangedEvent, value); }
         }
 
         /// <summary>
@@ -2058,7 +2102,7 @@ namespace Avalonia.Controls
                     forceHorizontalScroll: true);
             }
         }
-        
+
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
             base.OnAttachedToVisualTree(e);
@@ -2167,7 +2211,7 @@ namespace Avalonia.Controls
 
             return desiredSize;
         }
-        
+
         /// <inheritdoc/>
         protected override void OnDataContextBeginUpdate()
         {
@@ -2183,7 +2227,7 @@ namespace Avalonia.Controls
 
             NotifyDataContextPropertyForAllRowCells(GetAllRows(), false);
         }
-        
+
         /// <summary>
         /// Raises the BeginningEdit event.
         /// </summary>
@@ -3242,7 +3286,6 @@ namespace Avalonia.Controls
             }
         }
 
-        //TODO Styles
         private void AddNewCellPrivate(DataGridRow row, DataGridColumn column)
         {
             DataGridCell newCell = new DataGridCell();
@@ -3255,8 +3298,11 @@ namespace Avalonia.Controls
             {
                 newCell.OwningColumn = column;
                 newCell.IsVisible = column.IsVisible;
+                if (row.OwningGrid.CellTheme is {} cellTheme)
+                {
+                    newCell.SetValue(ThemeProperty, cellTheme, BindingPriority.TemplatedParent);
+                }
             }
-            //newCell.EnsureStyle(null);
             row.Cells.Insert(column.Index, newCell);
         }
 
@@ -4537,7 +4583,6 @@ namespace Avalonia.Controls
             FlushCurrentCellChanged();
         }
 
-        //TODO Styles
         private void PopulateCellContent(bool isCellEdited,
                                          DataGridColumn dataGridColumn,
                                          DataGridRow dataGridRow,
@@ -4575,7 +4620,7 @@ namespace Avalonia.Controls
                 dataGridCell.Content = element;
             }
 
-            
+
         }
 
         private void PreparingCellForEditPrivate(Control editingElement)

+ 20 - 19
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@@ -231,7 +231,7 @@ namespace Avalonia.Controls
         }
 
         /// <summary>
-        /// Gets or sets a value that indicates whether the user can change the column display position by 
+        /// Gets or sets a value that indicates whether the user can change the column display position by
         /// dragging the column header.
         /// </summary>
         /// <returns>
@@ -260,15 +260,15 @@ namespace Avalonia.Controls
         /// </returns>
         public bool CanUserResize
         {
-            get 
+            get
             {
                 return
                     CanUserResizeInternal ??
                     OwningGrid?.CanUserResizeColumns ??
                     DataGrid.DATAGRID_defaultCanUserResizeColumns;
             }
-            set 
-            { 
+            set
+            {
                 CanUserResizeInternal = value;
                 OwningGrid?.OnColumnCanUserResizeChanged(this);
             }
@@ -321,16 +321,16 @@ namespace Avalonia.Controls
         /// </returns>
         /// <exception cref="T:System.ArgumentOutOfRangeException">
         /// When setting this property, the specified value is less than -1 or equal to <see cref="F:System.Int32.MaxValue" />.
-        /// 
+        ///
         /// -or-
-        /// 
+        ///
         /// When setting this property on a column in a <see cref="T:Avalonia.Controls.DataGrid" />, the specified value is less than zero or greater than or equal to the number of columns in the <see cref="T:Avalonia.Controls.DataGrid" />.
         /// </exception>
         /// <exception cref="T:System.InvalidOperationException">
         /// When setting this property, the <see cref="T:Avalonia.Controls.DataGrid" /> is already making <see cref="P:Avalonia.Controls.DataGridColumn.DisplayIndex" /> adjustments. For example, this exception is thrown when you attempt to set <see cref="P:Avalonia.Controls.DataGridColumn.DisplayIndex" /> in a <see cref="E:Avalonia.Controls.DataGrid.ColumnDisplayIndexChanged" /> event handler.
-        /// 
+        ///
         /// -or-
-        /// 
+        ///
         /// When setting this property, the specified value would result in a frozen column being displayed in the range of unfrozen columns, or an unfrozen column being displayed in the range of frozen columns.
         /// </exception>
         public int DisplayIndex
@@ -401,7 +401,7 @@ namespace Avalonia.Controls
                 }
             }
         }
-        
+
         /// <summary>
         ///    Backing field for CellTheme property.
         /// </summary>
@@ -412,7 +412,7 @@ namespace Avalonia.Controls
                 (o, v) => o.CellTheme = v);
 
         /// <summary>
-        ///    Gets or sets the <see cref="DataGridColumnHeader"/> cell theme. 
+        ///    Gets or sets the <see cref="DataGridColumnHeader"/> cell theme.
         /// </summary>
         public ControlTheme CellTheme
         {
@@ -430,14 +430,14 @@ namespace Avalonia.Controls
                 (o, v) => o.Header = v);
 
         /// <summary>
-        ///    Gets or sets the <see cref="DataGridColumnHeader"/> content 
+        ///    Gets or sets the <see cref="DataGridColumnHeader"/> content
         /// </summary>
         public object Header
         {
             get { return _header; }
             set { SetAndRaise(HeaderProperty, ref _header, value); }
         }
-        
+
         /// <summary>
         ///    Backing field for Header property
         /// </summary>
@@ -455,7 +455,7 @@ namespace Avalonia.Controls
             get { return _headerTemplate; }
             set { SetAndRaise(HeaderTemplateProperty, ref _headerTemplate, value); }
         }
-        
+
         public bool IsAutoGenerated
         {
             get;
@@ -750,7 +750,7 @@ namespace Avalonia.Controls
         protected abstract IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding);
 
         /// <summary>
-        /// When overridden in a derived class, gets a read-only element that is bound to the column's 
+        /// When overridden in a derived class, gets a read-only element that is bound to the column's
         /// <see cref="P:Avalonia.Controls.DataGridBoundColumn.Binding" /> property value.
         /// </summary>
         /// <param name="cell">
@@ -765,7 +765,7 @@ namespace Avalonia.Controls
         protected abstract IControl GenerateElement(DataGridCell cell, object dataItem);
 
         /// <summary>
-        /// Called by a specific column type when one of its properties changed, 
+        /// Called by a specific column type when one of its properties changed,
         /// and its current cells need to be updated.
         /// </summary>
         /// <param name="propertyName">Indicates which property changed and caused this call</param>
@@ -882,9 +882,8 @@ namespace Avalonia.Controls
             {
                 LayoutRoundedWidth = ActualWidth;
             }
-        } 
+        }
 
-        //TODO Styles
         internal virtual DataGridColumnHeader CreateHeader()
         {
             var result = new DataGridColumnHeader
@@ -893,8 +892,10 @@ namespace Avalonia.Controls
             };
             result[!ContentControl.ContentProperty] = this[!HeaderProperty];
             result[!ContentControl.ContentTemplateProperty] = this[!HeaderTemplateProperty];
-            
-            //result.EnsureStyle(null);
+            if (OwningGrid.ColumnHeaderTheme is {} columnTheme)
+            {
+                result.SetValue(StyledElement.ThemeProperty, columnTheme, BindingPriority.TemplatedParent);
+            }
 
             return result;
         }

+ 9 - 6
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@@ -76,7 +76,7 @@ namespace Avalonia.Controls
         }
 
         /// <summary>
-        /// Initializes a new instance of the <see cref="T:Avalonia.Controls.Primitives.DataGridColumnHeader" /> class. 
+        /// Initializes a new instance of the <see cref="T:Avalonia.Controls.Primitives.DataGridColumnHeader" /> class.
         /// </summary>
         //TODO Implement
         public DataGridColumnHeader()
@@ -267,7 +267,7 @@ namespace Avalonia.Controls
                                 else
                                 {
                                     newSort = sort;
-                                } 
+                                }
 
                                 // changing direction should not affect sort order, so we replace this column's
                                 // sort description instead of just adding it to the end of the collection
@@ -603,7 +603,7 @@ namespace Avalonia.Controls
         }
 
         /// <summary>
-        /// Returns true if the mouse is 
+        /// Returns true if the mouse is
         /// - to the left of the element, or within the left half of the element
         /// and
         /// - within the vertical range of the element, or ignoreVertical == true
@@ -663,16 +663,19 @@ namespace Avalonia.Controls
             IsMouseOver = false;
         }
 
-        //TODO Styles DragIndicator
         private void OnMouseMove_BeginReorder(Point mousePosition)
         {
-            DataGridColumnHeader dragIndicator = new DataGridColumnHeader
+            var dragIndicator = new DataGridColumnHeader
             {
                 OwningColumn = OwningColumn,
                 IsEnabled = false,
                 Content = Content,
                 ContentTemplate = ContentTemplate
             };
+            if (OwningGrid.ColumnHeaderTheme is {} columnHeaderTheme)
+            {
+                dragIndicator.SetValue(ThemeProperty, columnHeaderTheme, BindingPriority.TemplatedParent);
+            }
 
             dragIndicator.PseudoClasses.Add(":dragIndicator");
 
@@ -720,7 +723,7 @@ namespace Avalonia.Controls
             {
                 return;
             }
-            
+
             //handle entry into reorder mode
             if (_dragMode == DragMode.MouseDown && _dragColumn == null && _lastMousePositionHeaders != null && (distanceFromRight > DATAGRIDCOLUMNHEADER_resizeRegionWidth && distanceFromLeft > DATAGRIDCOLUMNHEADER_resizeRegionWidth))
             {

+ 11 - 14
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@@ -89,7 +89,7 @@ namespace Avalonia.Controls
                 o => o.IsValid);
 
         /// <summary>
-        /// Gets a value that indicates whether the data in a row is valid. 
+        /// Gets a value that indicates whether the data in a row is valid.
         /// </summary>
         public bool IsValid
         {
@@ -130,7 +130,7 @@ namespace Avalonia.Controls
         }
 
         /// <summary>
-        /// Initializes a new instance of the <see cref="T:Avalonia.Controls.DataGridRow" /> class. 
+        /// Initializes a new instance of the <see cref="T:Avalonia.Controls.DataGridRow" /> class.
         /// </summary>
         public DataGridRow()
         {
@@ -240,7 +240,6 @@ namespace Avalonia.Controls
             private set;
         }
 
-        //TODO Styles
         internal DataGridCell FillerCell
         {
             get
@@ -252,7 +251,10 @@ namespace Avalonia.Controls
                         IsVisible = false,
                         OwningRow = this
                     };
-                    //_fillerCell.EnsureStyle(null);
+                    if (OwningGrid.CellTheme is {} cellTheme)
+                    {
+                        _fillerCell.SetValue(ThemeProperty, cellTheme, BindingPriority.TemplatedParent);
+                    }
                     if (_cellsElement != null)
                     {
                         _cellsElement.Children.Add(_fillerCell);
@@ -506,7 +508,7 @@ namespace Avalonia.Controls
         }
 
         /// <summary>
-        /// Measures the children of a <see cref="T:Avalonia.Controls.DataGridRow" /> to 
+        /// Measures the children of a <see cref="T:Avalonia.Controls.DataGridRow" /> to
         /// prepare for arranging them during the <see cref="M:System.Windows.FrameworkElement.ArrangeOverride(System.Windows.Size)" /> pass.
         /// </summary>
         /// <param name="availableSize">
@@ -709,8 +711,6 @@ namespace Avalonia.Controls
             }
         }
 
-        // Set the proper style for the Header by walking up the Style hierarchy
-        //TODO Styles
         internal void EnsureHeaderStyleAndVisibility(Styling.Style previousStyle)
         {
             if (_headerElement != null && OwningGrid != null)
@@ -785,7 +785,7 @@ namespace Avalonia.Controls
             OwningGrid?.OnRowDetailsChanged();
         }
 
-        // Returns the actual template that should be sued for Details: either explicity set on this row 
+        // Returns the actual template that should be sued for Details: either explicity set on this row
         // or inherited from the DataGrid
         private IDataTemplate ActualDetailsTemplate
         {
@@ -890,7 +890,7 @@ namespace Avalonia.Controls
         //TODO Cleanup
         double? _previousDetailsHeight = null;
 
-        //TODO Animation 
+        //TODO Animation
         private void DetailsContent_HeightChanged(double newValue)
         {
             if (_previousDetailsHeight.HasValue)
@@ -907,7 +907,7 @@ namespace Avalonia.Controls
 
                         _detailsElement.ContentHeight = newValue;
 
-                        // Calling this when details are not visible invalidates during layout when we have no work 
+                        // Calling this when details are not visible invalidates during layout when we have no work
                         // to do.  In certain scenarios, this could cause a layout cycle
                         OnRowDetailsChanged();
                     }
@@ -1060,7 +1060,7 @@ namespace Avalonia.Controls
                 }
             }
         }
-        
+
 
         protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
@@ -1084,7 +1084,4 @@ namespace Avalonia.Controls
         }
 
     }
-
-    //TODO Styles
-
 }

+ 6 - 8
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@@ -53,7 +53,7 @@ namespace Avalonia.Controls
             AvaloniaProperty.Register<DataGridRowGroupHeader, string>(nameof(PropertyName));
 
         /// <summary>
-        /// Gets or sets the name of the property that this <see cref="T:Avalonia.Controls.DataGrid" /> row is bound to. 
+        /// Gets or sets the name of the property that this <see cref="T:Avalonia.Controls.DataGrid" /> row is bound to.
         /// </summary>
         public string PropertyName
         {
@@ -85,8 +85,8 @@ namespace Avalonia.Controls
         }
 
         /// <summary>
-        /// Gets or sets a value that indicates the amount that the 
-        /// children of the <see cref="T:Avalonia.Controls.RowGroupHeader" /> are indented. 
+        /// Gets or sets a value that indicates the amount that the
+        /// children of the <see cref="T:Avalonia.Controls.RowGroupHeader" /> are indented.
         /// </summary>
         public double SublevelIndent
         {
@@ -327,9 +327,9 @@ namespace Avalonia.Controls
             {
                 double xClip = Math.Round(frozenLeftEdge - childLeftEdge);
                 var rg = new RectangleGeometry();
-                rg.Rect = 
-                    new Rect(xClip, 0, 
-                        Math.Max(0, child.Bounds.Width - xClip), 
+                rg.Rect =
+                    new Rect(xClip, 0,
+                        Math.Max(0, child.Bounds.Width - xClip),
                         child.Bounds.Height);
                 child.Clip = rg;
             }
@@ -348,8 +348,6 @@ namespace Avalonia.Controls
             }
         }
 
-        //TODO Styles
-        //internal void EnsureHeaderStyleAndVisibility(Style previousStyle)
         internal void EnsureHeaderVisibility()
         {
             if (_headerElement != null && OwningGrid != null)

+ 38 - 34
src/Avalonia.Controls.DataGrid/DataGridRows.cs

@@ -14,6 +14,9 @@ using System.Collections.Specialized;
 using System.ComponentModel;
 using System.Diagnostics;
 using System.Linq;
+using Avalonia.Data;
+using Avalonia.Styling;
+using JetBrains.Annotations;
 
 namespace Avalonia.Controls
 {
@@ -117,7 +120,7 @@ namespace Avalonia.Controls
                     detailsCount += GetDetailsCountInclusive(DisplayData.LastScrollingSlot + 1, SlotCount - 1);
                 }
 
-                // 
+                //
                 double totalDetailsHeight = detailsCount * RowDetailsHeightEstimate;
 
                 return totalRowsHeight + totalDetailsHeight;
@@ -163,7 +166,7 @@ namespace Avalonia.Controls
         }
 
         /// <summary>
-        /// Clears the entire selection except the indicated row. Displayed rows are deselected explicitly to 
+        /// Clears the entire selection except the indicated row. Displayed rows are deselected explicitly to
         /// visualize potential transition effects. The row indicated is selected if it is not already.
         /// </summary>
         internal void ClearRowSelection(int slotException, bool setAnchorSlot)
@@ -270,10 +273,10 @@ namespace Avalonia.Controls
             bool isRow = rowIndex != -1;
             if (isCollapsed)
             {
-                InsertElement(slot, 
-                    element: null, 
+                InsertElement(slot,
+                    element: null,
                     updateVerticalScrollBarOnly: true,
-                    isCollapsed: true, 
+                    isCollapsed: true,
                     isRow: isRow);
             }
             else if (SlotIsDisplayed(slot))
@@ -285,18 +288,18 @@ namespace Avalonia.Controls
                 }
                 else
                 {
-                    InsertElement(slot, GenerateRowGroupHeader(slot, groupInfo), 
+                    InsertElement(slot, GenerateRowGroupHeader(slot, groupInfo),
                         updateVerticalScrollBarOnly: false,
-                        isCollapsed: false, 
+                        isCollapsed: false,
                         isRow: isRow);
                 }
             }
             else
             {
-                InsertElement(slot, 
+                InsertElement(slot,
                     element: null,
                     updateVerticalScrollBarOnly: _vScrollBar == null || _vScrollBar.IsVisible,
-                    isCollapsed: false, 
+                    isCollapsed: false,
                     isRow: isRow);
             }
         }
@@ -417,7 +420,7 @@ namespace Avalonia.Controls
             if (scrolledHorizontally && DisplayData.FirstScrollingSlot <= slot && DisplayData.LastScrollingSlot >= slot)
             {
                 // If the slot is displayed and we scrolled horizontally, column virtualization could cause the rows to grow.
-                // As a result we need to force measure on the rows we're displaying and recalculate our First and Last slots 
+                // As a result we need to force measure on the rows we're displaying and recalculate our First and Last slots
                 // so they're accurate
                 foreach (DataGridRow row in DisplayData.GetScrollingRows())
                 {
@@ -455,7 +458,7 @@ namespace Avalonia.Controls
                 deltaY -= GetSlotElementsHeight(slot, firstFullSlot);
                 if (DisplayData.FirstScrollingSlot - slot > 1)
                 {
-                    // 
+                    //
 
                     ResetDisplayedRows();
                 }
@@ -519,7 +522,7 @@ namespace Avalonia.Controls
                 _verticalOffset = NegVerticalOffset;
             }
 
-            // 
+            //
             Debug.Assert(MathUtilities.LessThanOrClose(NegVerticalOffset, _verticalOffset));
 
             SetVerticalOffset(_verticalOffset);
@@ -1025,6 +1028,10 @@ namespace Avalonia.Controls
                 dataGridRow.Slot = slot;
                 dataGridRow.OwningGrid = this;
                 dataGridRow.DataContext = dataContext;
+                if (RowTheme is {} rowTheme)
+                {
+                    dataGridRow.SetValue(ThemeProperty, rowTheme, BindingPriority.TemplatedParent);
+                }
                 CompleteCellsCollection(dataGridRow);
 
                 OnLoadingRow(new DataGridRowEventArgs(dataGridRow));
@@ -1104,7 +1111,7 @@ namespace Avalonia.Controls
 
         /// <summary>
         /// Checks if the row for the provided dataContext has been generated and is present
-        /// in either the loaded rows, pre-fetched rows, or editing row. 
+        /// in either the loaded rows, pre-fetched rows, or editing row.
         /// The displayed rows are *not* searched. Returns null if the row does not belong to those 3 categories.
         /// </summary>
         private DataGridRow GetGeneratedRow(object dataContext)
@@ -1152,8 +1159,8 @@ namespace Avalonia.Controls
             }
             else
             {
-                // If we're grouping, the GroupLevel needs to be fixed later by methods calling this 
-                // which end up inserting rows. We don't do it here because elements could be inserted 
+                // If we're grouping, the GroupLevel needs to be fixed later by methods calling this
+                // which end up inserting rows. We don't do it here because elements could be inserted
                 // from top to bottom or bottom to up so it's better to do in one pass
                 slotElement = GenerateRow(RowIndexFromSlot(slot), slot);
             }
@@ -1161,7 +1168,6 @@ namespace Avalonia.Controls
             return slotElement;
         }
 
-        //TODO Styles
         private void InsertDisplayedElement(int slot, Control element, bool wasNewlyAdded, bool updateSlotInformation)
         {
             // We can only support creating new rows that are adjacent to the currently visible rows
@@ -1479,7 +1485,6 @@ namespace Avalonia.Controls
             }
         }
 
-        //TODO Styles
         // Makes sure the row shows the proper visuals for selection, currency, details, etc.
         private void LoadRowVisualsForDisplay(DataGridRow row)
         {
@@ -1694,7 +1699,7 @@ namespace Avalonia.Controls
                         {
                             // Figure out what row we've scrolled down to and update the value for NegVerticalOffset
                             NegVerticalOffset = 0;
-                            // 
+                            //
                             if (height > 2 * CellsHeight &&
                                 (RowDetailsVisibilityMode != DataGridRowDetailsVisibilityMode.VisibleWhenSelected || RowDetailsTemplate == null))
                             {
@@ -1755,7 +1760,7 @@ namespace Avalonia.Controls
                         // Figure out what row we've scrolled up to and update the value for NegVerticalOffset
                         deltaY = -NegVerticalOffset;
                         NegVerticalOffset = 0;
-                        // 
+                        //
 
                         if (height < -2 * CellsHeight &&
                             (RowDetailsVisibilityMode != DataGridRowDetailsVisibilityMode.VisibleWhenSelected || RowDetailsTemplate == null))
@@ -1813,7 +1818,7 @@ namespace Avalonia.Controls
                     if (MathUtilities.GreaterThanOrClose(0, newVerticalOffset) && newFirstScrollingSlot != 0)
                     {
                         // We've scrolled to the top of the ScrollBar, automatically place the user at the very top
-                        // of the DataGrid.  If this produces very odd behavior, evaluate the RowHeight estimate.  
+                        // of the DataGrid.  If this produces very odd behavior, evaluate the RowHeight estimate.
                         // strategy. For most data, this should be unnoticeable.
                         ResetDisplayedRows();
                         NegVerticalOffset = 0;
@@ -1994,7 +1999,6 @@ namespace Avalonia.Controls
             VisibleSlotCount = 0;
         }
 
-        //TODO Styles
         private void UnloadRow(DataGridRow dataGridRow)
         {
             Debug.Assert(dataGridRow != null);
@@ -2010,16 +2014,13 @@ namespace Avalonia.Controls
             OnUnloadingRow(new DataGridRowEventArgs(dataGridRow));
             bool recycleRow = CurrentSlot != dataGridRow.Index;
 
-            // Don't recycle if the row has a custom Style set
-            //recycleRow &= (dataGridRow.Style == null || dataGridRow.Style == RowStyle);
-
             if (recycleRow)
             {
                 DisplayData.AddRecyclableRow(dataGridRow);
             }
             else
             {
-                // 
+                //
                 _rowsPresenter.Children.Remove(dataGridRow);
                 dataGridRow.DetachFromDataGrid(false);
             }
@@ -2240,10 +2241,10 @@ namespace Avalonia.Controls
                             group.Items.CollectionChanged += CollectionViewGroup_CollectionChanged;
                         }
                         var newGroupInfo = new DataGridRowGroupInfo(group, true, parentGroupInfo.Level + 1, insertSlot, insertSlot);
-                        InsertElementAt(insertSlot, 
-                            rowIndex: -1, 
-                            item: null, 
-                            groupInfo: newGroupInfo, 
+                        InsertElementAt(insertSlot,
+                            rowIndex: -1,
+                            item: null,
+                            groupInfo: newGroupInfo,
                             isCollapsed: isCollapsed);
                         RowGroupHeadersTable.AddValue(insertSlot, newGroupInfo);
                     }
@@ -2256,9 +2257,9 @@ namespace Avalonia.Controls
                         {
                             AutoGenerateColumnsPrivate();
                         }
-                        InsertElementAt(insertSlot, rowIndex, 
+                        InsertElementAt(insertSlot, rowIndex,
                             item: e.NewItems[0],
-                            groupInfo: null, 
+                            groupInfo: null,
                             isCollapsed: isCollapsed);
                     }
 
@@ -2448,13 +2449,12 @@ namespace Avalonia.Controls
             VisibleSlotCount = SlotCount;
         }
 
-        //TODO Styles
         private void RefreshRowGroupHeaders()
         {
             if (DataConnection.CollectionView != null
                 && DataConnection.CollectionView.CanGroup
                 && DataConnection.CollectionView.Groups != null
-                && DataConnection.CollectionView.IsGrouping 
+                && DataConnection.CollectionView.IsGrouping
                 && DataConnection.CollectionView.GroupingDepth > 0)
             {
                 // Initialize our array for the height of the RowGroupHeaders by Level.
@@ -2476,7 +2476,7 @@ namespace Avalonia.Controls
                     double indent;
                     for (int i = 0; i < groupLevelCount; i++)
                     {
-                        indent = DATAGRID_defaultRowGroupSublevelIndent; 
+                        indent = DATAGRID_defaultRowGroupSublevelIndent;
                         RowGroupSublevelIndents[i] = indent;
                         if (i > 0)
                         {
@@ -2742,6 +2742,10 @@ namespace Avalonia.Controls
             groupHeader.RowGroupInfo = rowGroupInfo;
             groupHeader.DataContext = rowGroupInfo.CollectionViewGroup;
             groupHeader.Level = rowGroupInfo.Level;
+            if (RowGroupTheme is {} rowGroupTheme)
+            {
+                groupHeader.SetValue(ThemeProperty, rowGroupTheme, BindingPriority.TemplatedParent);
+            }
 
             // Set the RowGroupHeader's PropertyName. Unfortunately, CollectionViewGroup doesn't have this
             // so we have to set it manually

+ 1 - 6
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@@ -232,7 +232,7 @@
         <Setter Property="Data" Value="{StaticResource DataGridSortIconDescendingPath}" />
       </Style>
     </ControlTheme>
-    
+
     <ControlTheme x:Key="DataGridTopLeftColumnHeader" TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
       <Setter Property="Template">
         <ControlTemplate>
@@ -337,10 +337,6 @@
         </ControlTemplate>
       </Setter>
 
-      <Style Selector="^:nth-child(even)">
-        <Setter Property="Background" Value="{Binding $parent[DataGrid].AlternatingRowBackground}" />
-      </Style>
-
       <Style Selector="^:invalid">
         <Style Selector="^ /template/ Rectangle#InvalidVisualElement">
           <Setter Property="Opacity" Value="0.4" />
@@ -484,7 +480,6 @@
 
     <ControlTheme x:Key="{x:Type DataGrid}" TargetType="DataGrid">
       <Setter Property="RowBackground" Value="Transparent" />
-      <Setter Property="AlternatingRowBackground" Value="Transparent" />
       <Setter Property="HeadersVisibility" Value="Column" />
       <Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
       <Setter Property="VerticalScrollBarVisibility" Value="Auto" />

+ 0 - 5
src/Avalonia.Controls.DataGrid/Themes/Simple.xaml

@@ -200,10 +200,6 @@
         </ControlTemplate>
       </Setter>
 
-      <Style Selector="^:nth-child(even)">
-        <Setter Property="Background" Value="{Binding $parent[DataGrid].AlternatingRowBackground}" />
-      </Style>
-
       <Style Selector="^ /template/ Rectangle#BackgroundRectangle">
         <Setter Property="IsVisible" Value="False" />
         <Setter Property="Fill" Value="{DynamicResource HighlightBrush}" />
@@ -300,7 +296,6 @@
     <ControlTheme x:Key="{x:Type DataGrid}"
                   TargetType="DataGrid">
       <Setter Property="RowBackground" Value="{DynamicResource ThemeAccentBrush4}" />
-      <Setter Property="AlternatingRowBackground" Value="#00FFFFFF" />
       <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
       <Setter Property="HeadersVisibility" Value="Column" />
       <Setter Property="HorizontalScrollBarVisibility" Value="Auto" />

+ 1 - 1
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@@ -216,7 +216,7 @@ namespace Avalonia.Controls.Generators
                 result.SetValue(
                     StyledElement.ThemeProperty,
                     ItemContainerTheme,
-                    BindingPriority.TemplatedParent);
+                    BindingPriority.Template);
             }
 
             return result;

+ 20 - 39
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -5,6 +5,7 @@ using Avalonia.Interactivity;
 using Avalonia.Logging;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
 
@@ -395,56 +396,36 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        internal override void DetachControlThemeFromTemplateChildren(ControlTheme theme)
+        private protected override void OnControlThemeChanged()
         {
-            static ControlTheme? GetControlTheme(StyleBase style)
-            {
-                var s = style;
+            base.OnControlThemeChanged();
 
-                while (s is not null)
+            var count = VisualChildren.Count;
+            for (var i = 0; i < count; ++i)
+            {
+                if (VisualChildren[i] is StyledElement child &&
+                    child.TemplatedParent == this)
                 {
-                    if (s is ControlTheme c)
-                        return c;
-                    s = s.Parent as StyleBase;
+                    child.OnTemplatedParentControlThemeChanged();
                 }
-
-                return null;
             }
+        }
 
-            static void Detach(Visual control, ITemplatedControl templatedParent, ControlTheme theme)
-            {
-                var valueStore = control.GetValueStore();
-                var count = valueStore.Frames.Count;
-
-                if (control != templatedParent)
-                {
-                    valueStore.BeginStyling();
-
-                    for (var i = count - 1; i >= 0; --i)
-                    {
-                        if (valueStore.Frames[i] is StyleInstance si &&
-                            si.Source is StyleBase style &&
-                            GetControlTheme(style) == theme)
-                        {
-                            valueStore.RemoveFrame(si);
-                        }
-                    }
-
-                    valueStore.EndStyling();
-                }
+        internal override void OnTemplatedParentControlThemeChanged()
+        {
+            base.OnTemplatedParentControlThemeChanged();
 
-                var children = ((IVisual)control).VisualChildren;
-                count = children.Count;
+            var count = VisualChildren.Count;
+            var templatedParent = TemplatedParent;
 
-                for (var i = 0; i < count; i++)
+            for (var i = 0; i < count; ++i)
+            {
+                if (VisualChildren[i] is TemplatedControl child &&
+                    child.TemplatedParent == templatedParent)
                 {
-                    if (children[i] is Visual v &&
-                        v.TemplatedParent == templatedParent)
-                        Detach(v, templatedParent, theme);
+                    child.OnTemplatedParentControlThemeChanged();
                 }
             }
-
-            Detach(this, this, theme);
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplatePriorityTransformer.cs

@@ -42,7 +42,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 if (priorityValueSetters.Count > 0)
                 {
                     prop.PossibleSetters = priorityValueSetters;
-                    prop.Values.Insert(0, new XamlConstantNode(node, bindingPriorityType, (int)BindingPriority.TemplatedParent));
+                    prop.Values.Insert(0, new XamlConstantNode(node, bindingPriorityType, (int)BindingPriority.Template));
                 }
             }
 

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@@ -27,7 +27,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
         public IBinding ProvideValue(IServiceProvider serviceProvider)
         {
             if (serviceProvider.IsInControlTemplate())
-                _priority = BindingPriority.TemplatedParent;
+                _priority = BindingPriority.Template;
 
             var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
 

+ 1 - 1
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@@ -51,7 +51,7 @@ namespace Avalonia.Data
                 return new InstancedBinding(
                     this,
                     Mode == BindingMode.Default ? BindingMode.OneWay : Mode,
-                    BindingPriority.TemplatedParent);
+                    BindingPriority.Template);
             }
             else
             {

+ 2 - 1
tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs

@@ -5,6 +5,7 @@ using Avalonia.Controls.Shapes;
 using Avalonia.Data;
 using Avalonia.Layout;
 using Avalonia.Media;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Moq;
@@ -435,7 +436,7 @@ namespace Avalonia.Base.UnitTests.Animation
                 }
             };
 
-            style.TryAttach(control, control);
+            StyleHelpers.TryAttach(style, control);
 
             // Which means that the transition state hasn't been initialized with the new
             // Transitions when the Opacity change notification gets raised here.

+ 1 - 1
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs

@@ -251,7 +251,7 @@ namespace Avalonia.Base.UnitTests
         {
             Class1 target = new Class1();
 
-            target.SetValue(Class1.FooProperty, "one", BindingPriority.TemplatedParent);
+            target.SetValue(Class1.FooProperty, "one", BindingPriority.Template);
             Assert.Equal("one", target.GetValue(Class1.FooProperty));
             target.SetValue(Class1.FooProperty, "two", BindingPriority.Style);
             Assert.Equal("one", target.GetValue(Class1.FooProperty));

+ 1 - 1
tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs

@@ -117,7 +117,7 @@ namespace Avalonia.Base.UnitTests.PropertyStore
 
         private static StyleInstance InstanceStyle(Style style, StyledElement target)
         {
-            var result = new StyleInstance(style, null);
+            var result = new StyleInstance(style, null, FrameType.Style);
 
             foreach (var setter in style.Setters)
                 result.Add(setter.Instance(result, target));

+ 2 - 1
tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs

@@ -6,6 +6,7 @@ using Avalonia.Controls.Templates;
 using Avalonia.Data;
 using Avalonia.Data.Converters;
 using Avalonia.Media;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Moq;
@@ -503,7 +504,7 @@ namespace Avalonia.Base.UnitTests.Styling
 
         private void Apply(Style style, Control control)
         {
-            style.TryAttach(control, null);
+            StyleHelpers.TryAttach(style, control);
         }
 
         private void Apply(Setter setter, Control control)

+ 54 - 26
tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs

@@ -5,6 +5,7 @@ using Avalonia.Base.UnitTests.Animation;
 using Avalonia.Controls;
 using Avalonia.Controls.Templates;
 using Avalonia.Data;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Moq;
@@ -27,7 +28,7 @@ namespace Avalonia.Base.UnitTests.Styling
 
             var target = new Class1();
 
-            style.TryAttach(target, null);
+            StyleHelpers.TryAttach(style, target);
 
             Assert.Equal("Foo", target.Foo);
         }
@@ -45,7 +46,7 @@ namespace Avalonia.Base.UnitTests.Styling
 
             var target = new Class1();
 
-            style.TryAttach(target, null);
+            StyleHelpers.TryAttach(style, target);
             Assert.Equal("foodefault", target.Foo);
             target.Classes.Add("foo");
             Assert.Equal("Foo", target.Foo);
@@ -66,7 +67,7 @@ namespace Avalonia.Base.UnitTests.Styling
 
             var target = new Class1();
 
-            style.TryAttach(target, target);
+            StyleHelpers.TryAttach(style, target);
 
             Assert.Equal("Foo", target.Foo);
         }
@@ -92,7 +93,7 @@ namespace Avalonia.Base.UnitTests.Styling
             var target = new Class1();
             var other = new Class1();
 
-            style.TryAttach(target, other);
+            StyleHelpers.TryAttach(style, target, host: other);
 
             Assert.Equal("foodefault", target.Foo);
         }
@@ -113,7 +114,7 @@ namespace Avalonia.Base.UnitTests.Styling
                 Foo = "Original",
             };
 
-            style.TryAttach(target, null);
+            StyleHelpers.TryAttach(style, target);
             Assert.Equal("Original", target.Foo);
         }
 
@@ -577,7 +578,7 @@ namespace Avalonia.Base.UnitTests.Styling
                 Child = border = new Border(),
             };
 
-            style.TryAttach(border, null);
+            StyleHelpers.TryAttach(style, border);
 
             Assert.Equal(new Thickness(4), border.BorderThickness);
             root.Child = null;
@@ -617,15 +618,15 @@ namespace Avalonia.Base.UnitTests.Styling
             var root = new TestRoot
             {
                 Styles =
+                {
+                    new Style(x => x.OfType<Border>())
                     {
-                        new Style(x => x.OfType<Border>())
+                        Setters =
                         {
-                            Setters =
-                            {
-                                new Setter(Border.BorderThicknessProperty, new Thickness(4)),
-                            }
+                            new Setter(Border.BorderThicknessProperty, new Thickness(4)),
                         }
-                    },
+                    }
+                },
                 Child = border,
             };
 
@@ -635,9 +636,9 @@ namespace Avalonia.Base.UnitTests.Styling
             root.Styles.Add(new Style(x => x.OfType<Border>())
             {
                 Setters =
-                    {
-                        new Setter(Border.BorderThicknessProperty, new Thickness(6)),
-                    }
+                {
+                    new Setter(Border.BorderThicknessProperty, new Thickness(6)),
+                }
             });
 
             root.Measure(Size.Infinity);
@@ -651,18 +652,18 @@ namespace Avalonia.Base.UnitTests.Styling
             var root = new TestRoot
             {
                 Styles =
+                {
+                    new Styles
                     {
-                        new Styles
+                        new Style(x => x.OfType<Border>())
                         {
-                            new Style(x => x.OfType<Border>())
+                            Setters =
                             {
-                                Setters =
-                                {
-                                    new Setter(Border.BorderThicknessProperty, new Thickness(4)),
-                                }
+                                new Setter(Border.BorderThicknessProperty, new Thickness(4)),
                             }
                         }
-                    },
+                    }
+                },
                 Child = border,
             };
 
@@ -749,7 +750,34 @@ namespace Avalonia.Base.UnitTests.Styling
         }
 
         [Fact]
-        public void DetachStyles_Should_Detach_Activator()
+        public void Adding_Style_With_No_Setters_Or_Animations_Should_Not_Invalidate_Styles()
+        {
+            var border = new Border();
+            var root = new TestRoot
+            {
+                Styles =
+                    {
+                        new Style(x => x.OfType<Border>())
+                        {
+                            Setters =
+                            {
+                                new Setter(Border.BorderThicknessProperty, new Thickness(4)),
+                            }
+                        }
+                    },
+                Child = border,
+            };
+
+            root.Measure(Size.Infinity);
+            Assert.Equal(new Thickness(4), border.BorderThickness);
+
+            root.Styles.Add(new Style(x => x.OfType<Border>()));
+
+            Assert.Equal(new Thickness(4), border.BorderThickness);
+        }
+
+        [Fact]
+        public void Invalidating_Styles_Should_Detach_Activator()
         {
             Style style = new Style(x => x.OfType<Class1>().Class("foo"))
             {
@@ -761,11 +789,11 @@ namespace Avalonia.Base.UnitTests.Styling
 
             var target = new Class1();
 
-            style.TryAttach(target, null);
+            StyleHelpers.TryAttach(style, target);
 
             Assert.Equal(1, target.Classes.ListenerCount);
 
-            ((IStyleable)target).DetachStyles();
+            target.InvalidateStyles(recurse: false);
 
             Assert.Equal(0, target.Classes.ListenerCount);
         }
@@ -874,7 +902,7 @@ namespace Avalonia.Base.UnitTests.Styling
             var clock = new TestClock();
             var target = new Class1 { Clock = clock };
 
-            style.TryAttach(target, null);
+            StyleHelpers.TryAttach(style, target);
 
             Assert.Equal(0.0, target.Double);
 

+ 207 - 11
tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs

@@ -23,7 +23,7 @@ public class StyledElementTests_Theming
 
             Assert.Null(target.Template);
 
-            var root = CreateRoot(target);
+            CreateRoot(target);
             Assert.NotNull(target.Template);
 
             var border = Assert.IsType<Border>(target.VisualChild);
@@ -43,7 +43,7 @@ public class StyledElementTests_Theming
 
             Assert.Null(target.Template);
 
-            var root = CreateRoot(target);
+            CreateRoot(target);
             Assert.NotNull(target.Template);
 
             var border = Assert.IsType<Border>(target.VisualChild);
@@ -57,7 +57,7 @@ public class StyledElementTests_Theming
         public void Theme_Is_Detached_When_Theme_Property_Cleared()
         {
             var target = CreateTarget();
-            var root = CreateRoot(target);
+            CreateRoot(target);
 
             Assert.NotNull(target.Template);
 
@@ -66,7 +66,47 @@ public class StyledElementTests_Theming
         }
 
         [Fact]
-        public void Theme_Is_Detached_From_Template_Controls_When_Theme_Property_Cleared()
+        public void Setting_Explicit_Theme_Detaches_Default_Theme()
+        {
+            var target = new ThemedControl();
+            var root = new TestRoot
+            {
+                Resources = { { typeof(ThemedControl), CreateTheme() } },
+                Child = target,
+            };
+
+            root.LayoutManager.ExecuteInitialLayoutPass();
+
+            Assert.Equal("theme", target.Tag);
+
+            target.Theme = new ControlTheme(typeof(ThemedControl))
+            {
+                Setters =
+                {
+                    new Setter(ThemedControl.BackgroundProperty, Brushes.Yellow),
+                }
+            };
+
+            root.LayoutManager.ExecuteLayoutPass();
+
+            Assert.Null(target.Tag);
+            Assert.Equal(Brushes.Yellow, target.Background);
+        }
+
+        [Fact]
+        public void Unrelated_Styles_Are_Not_Detached_When_Theme_Property_Cleared()
+        {
+            var target = CreateTarget();
+            CreateRoot(target, createAdditionalStyles: true);
+
+            Assert.Equal("style", target.Tag);
+
+            target.Theme = null;
+            Assert.Equal("style", target.Tag);
+        }
+        
+        [Fact]
+        public void TemplatedParent_Theme_Is_Detached_From_Template_Controls_When_Theme_Property_Cleared()
         {
             var theme = new ControlTheme
             {
@@ -93,10 +133,115 @@ public class StyledElementTests_Theming
 
             target.Theme = null;
 
-            Assert.IsType<Canvas>(target.VisualChild);
+            Assert.Same(canvas, target.VisualChild);
             Assert.Null(canvas.Background);
         }
 
+        [Fact]
+        public void Primary_Theme_Is_Not_Detached_From_Template_Controls_When_Theme_Property_Cleared()
+        {
+            var templatedParentTheme = new ControlTheme
+            {
+                TargetType = typeof(ThemedControl),
+                Children =
+                {
+                    new Style(x => x.Nesting().Template().OfType<Button>())
+                    {
+                        Setters =
+                        {
+                            new Setter(Panel.BackgroundProperty, Brushes.Red),
+                        }
+                    },
+                }
+            };
+
+            var childTheme = new ControlTheme
+            {
+                TargetType = typeof(Button),
+                Setters =
+                {
+                    new Setter(TemplatedControl.ForegroundProperty, Brushes.Green),
+                }
+            };
+
+            var target = CreateTarget(templatedParentTheme);
+            target.Template = new FuncControlTemplate<ThemedControl>((o, n) => new Button
+            {
+                Theme = childTheme,
+            });
+
+            var root = CreateRoot(target, createAdditionalStyles: true);
+
+            var templateChild = Assert.IsType<Button>(target.VisualChild);
+            Assert.Equal(Brushes.Red, templateChild.Background);
+            Assert.Equal(Brushes.Green, templateChild.Foreground);
+
+            target.Theme = null;
+
+            Assert.Null(templateChild.Background);
+            Assert.Equal(Brushes.Green, templateChild.Foreground);
+        }
+
+        [Fact]
+        public void TemplatedParent_Theme_Is_Not_Detached_From_Template_Controls_When_Primary_Theme_Property_Cleared()
+        {
+            var templatedParentTheme = new ControlTheme
+            {
+                TargetType = typeof(ThemedControl),
+                Children =
+                {
+                    new Style(x => x.Nesting().Template().OfType<Button>())
+                    {
+                        Setters =
+                        {
+                            new Setter(Panel.BackgroundProperty, Brushes.Red),
+                        }
+                    },
+                }
+            };
+
+            var childTheme = new ControlTheme
+            {
+                TargetType = typeof(Button),
+                Setters =
+                {
+                    new Setter(Button.TagProperty, "childTheme"),
+                }
+            };
+
+            var target = CreateTarget(templatedParentTheme);
+            target.Template = new FuncControlTemplate<ThemedControl>((o, n) => new Button
+            {
+                Theme = childTheme,
+            });
+
+            var root = CreateRoot(target, createAdditionalStyles: true);
+
+            var templateChild = Assert.IsType<Button>(target.VisualChild);
+            Assert.Equal(Brushes.Red, templateChild.Background);
+            Assert.Equal("childTheme", templateChild.Tag);
+
+            templateChild.Theme = null;
+
+            Assert.Equal(Brushes.Red, templateChild.Background);
+            Assert.Null(templateChild.Tag);
+        }
+
+        [Fact]
+        public void Unrelated_Styles_Are_Not_Detached_From_Template_Controls_When_Theme_Property_Cleared()
+        {
+            var target = CreateTarget();
+            var root = CreateRoot(target, createAdditionalStyles: true);
+
+            var canvas = Assert.IsType<Border>(target.VisualChild);
+            Assert.Equal("style", canvas.Tag);
+
+            target.Theme = null;
+
+            Assert.Same(canvas, target.VisualChild);
+            Assert.Equal("style", canvas.Tag);
+        }
+
         [Fact]
         public void Theme_Is_Applied_On_Layout_After_Theme_Property_Changes()
         {
@@ -122,7 +267,7 @@ public class StyledElementTests_Theming
 
             Assert.Null(target.Template);
 
-            var root = CreateRoot(target);
+            CreateRoot(target);
             Assert.NotNull(target.Template);
             Assert.Equal(Brushes.Blue, target.BorderBrush);
 
@@ -135,6 +280,29 @@ public class StyledElementTests_Theming
             Assert.Equal(Brushes.Cyan, border.BorderBrush);
         }
 
+        [Fact]
+        public void Theme_Has_Lower_Priority_Than_Style()
+        {
+            var target = CreateTarget();
+            CreateRoot(target, createAdditionalStyles: true);
+
+            Assert.Equal("style", target.Tag);
+        }
+
+        [Fact]
+        public void Theme_Has_Lower_Priority_Than_Style_After_Change()
+        {
+            var target = CreateTarget();
+            var theme = target.Theme;
+            CreateRoot(target, createAdditionalStyles: true);
+
+            target.Theme = null;
+            target.Theme = theme;
+            target.ApplyStyling();
+
+            Assert.Equal("style", target.Tag);
+        }
+
         private static ThemedControl CreateTarget(ControlTheme? theme = null)
         {
             return new ThemedControl
@@ -143,9 +311,32 @@ public class StyledElementTests_Theming
             };
         }
 
-        private static TestRoot CreateRoot(IControl child)
+        private static TestRoot CreateRoot(
+            IControl child,
+            bool createAdditionalStyles = false)
         {
-            var result = new TestRoot(child);
+            var result = new TestRoot();
+
+            if (createAdditionalStyles)
+            {
+                result.Styles.Add(new Style(x => x.OfType<ThemedControl>())
+                {
+                    Setters =
+                    {
+                        new Setter(Control.TagProperty, "style"),
+                    }
+                });
+
+                result.Styles.Add(new Style(x => x.OfType<Border>())
+                {
+                    Setters =
+                    {
+                        new Setter(Control.TagProperty, "style"),
+                    }
+                });
+            }
+
+            result.Child = child;
             result.LayoutManager.ExecuteInitialLayoutPass();
             return result;
         }
@@ -157,7 +348,7 @@ public class StyledElementTests_Theming
         public void Implicit_Theme_Is_Applied_When_Attached_To_Logical_Tree()
         {
             var target = CreateTarget();
-            var root = CreateRoot(target);
+            CreateRoot(target);
             Assert.NotNull(target.Template);
 
             var border = Assert.IsType<Border>(target.VisualChild);
@@ -231,7 +422,7 @@ public class StyledElementTests_Theming
             Assert.Null(target.Theme);
             Assert.Null(target.Template);
 
-            var root = CreateRoot(target);
+            CreateRoot(target);
 
             Assert.NotNull(target.Theme);
             Assert.NotNull(target.Template);
@@ -348,6 +539,7 @@ public class StyledElementTests_Theming
             TargetType = typeof(ThemedControl),
             Setters =
             {
+                new Setter(Control.TagProperty, "theme"),
                 new Setter(TemplatedControl.TemplateProperty, template),
                 new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(5)),
             },
@@ -355,7 +547,11 @@ public class StyledElementTests_Theming
             {
                 new Style(x => x.Nesting().Template().OfType<Border>())
                 {
-                    Setters = { new Setter(Border.BackgroundProperty, Brushes.Red) }
+                    Setters = 
+                    { 
+                        new Setter(Border.BackgroundProperty, Brushes.Red),
+                        new Setter(Control.TagProperty, "theme"),
+                    }
                 },
                 new Style(x => x.Nesting().Class("foo").Template().OfType<Border>())
                 {

+ 1 - 1
tests/Avalonia.Benchmarks/Base/StyledPropertyBenchmark.cs

@@ -43,7 +43,7 @@ namespace Avalonia.Benchmarks.Base
 
             for (var i = 0; i < 100; ++i)
             {
-                obj.SetValue(StyledClass.IntValueProperty, obj.IntValue + 1, BindingPriority.TemplatedParent);
+                obj.SetValue(StyledClass.IntValueProperty, obj.IntValue + 1, BindingPriority.Template);
             }
         }
 

+ 149 - 0
tests/Avalonia.Benchmarks/Styling/ControlTheme_Change.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Styling;
+using Avalonia.UnitTests;
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Styling
+{
+    [MemoryDiagnoser]
+    public class ControlTheme_Change : IDisposable
+    {
+        private readonly IDisposable _app;
+        private readonly TestRoot _root;
+        private readonly TextBox _control;
+        private readonly ControlTheme _theme1;
+        private readonly ControlTheme _theme2;
+
+        public ControlTheme_Change()
+        {
+            _app = UnitTestApplication.Start(
+                TestServices.StyledWindow.With(
+                    renderInterface: new NullRenderingPlatform(),
+                    threadingInterface: new NullThreadingPlatform()));
+
+            // Simulate an application with a lot of styles by creating a tree of nested panels,
+            // each with a bunch of styles applied.
+            var (rootPanel, leafPanel) = CreateNestedPanels(10);
+
+            // We're benchmarking how long it takes to switch control theme on a TextBox in this
+            // situation.
+            var baseTheme = (ControlTheme)Application.Current.FindResource(typeof(TextBox)) ??
+                throw new Exception("Base TextBox theme not found.");
+
+            _theme1 = new ControlTheme(typeof(TextBox))
+            {
+                BasedOn = baseTheme,
+                Setters = { new Setter(TextBox.BackgroundProperty, Brushes.Red) },
+            };
+
+            _theme2 = new ControlTheme(typeof(TextBox))
+            {
+                BasedOn = baseTheme,
+                Setters = { new Setter(TextBox.BackgroundProperty, Brushes.Green) },
+            };
+
+            _control = new TextBox { Theme = _theme1 };
+            leafPanel.Children.Add(_control);
+
+            _root = new TestRoot(true, rootPanel)
+            {
+                Renderer = new NullRenderer(),
+            };
+
+            _root.LayoutManager.ExecuteInitialLayoutPass();
+        }
+
+        [Benchmark]
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public void Change_ControlTheme()
+        {
+            if (_control.Background != Brushes.Red)
+                throw new Exception("Invalid benchmark state");
+
+            _control.Theme = _theme2;
+            _root.LayoutManager.ExecuteLayoutPass();
+
+            if (_control.Background != Brushes.Green)
+                throw new Exception("Invalid benchmark state");
+
+            _control.Theme = _theme1;
+            _root.LayoutManager.ExecuteLayoutPass();
+
+            if (_control.Background != Brushes.Red)
+                throw new Exception("Invalid benchmark state");
+        }
+
+        public void Dispose()
+        {
+            _app.Dispose();
+        }
+
+        private static (Panel, Panel) CreateNestedPanels(int count)
+        {
+            var root = new Panel();
+            var last = root;
+
+            for (var i = 0; i < count; ++i)
+            {
+                var panel = new Panel();
+                panel.Styles.AddRange(CreateStyles());
+                last.Children.Add(panel);
+                last = panel;
+            }
+
+            return (root, last);
+        }
+
+        private static IEnumerable<IStyle> CreateStyles()
+        {
+            var types = new[]
+            {
+                typeof(Border),
+                typeof(Button),
+                typeof(ButtonSpinner),
+                typeof(Carousel),
+                typeof(CheckBox),
+                typeof(ComboBox),
+                typeof(ContentControl),
+                typeof(Expander),
+                typeof(ItemsControl),
+                typeof(Label),
+                typeof(ListBox),
+                typeof(ProgressBar),
+                typeof(RadioButton),
+                typeof(RepeatButton),
+                typeof(ScrollViewer),
+                typeof(Slider),
+                typeof(Spinner),
+                typeof(SplitView),
+                typeof(TextBox),
+                typeof(ToggleSwitch),
+                typeof(TreeView),
+                typeof(Viewbox),
+                typeof(Window),
+            };
+
+            foreach (var type in types)
+            {
+                yield return new Style(x => x.OfType(type))
+                {
+                    Setters = { new Setter(Control.TagProperty, type.Name) }
+                };
+
+                yield return new Style(x => x.OfType(type).Class("foo"))
+                {
+                    Setters = { new Setter(Control.TagProperty, type.Name + " foo") }
+                };
+
+                yield return new Style(x => x.OfType(type).Class("bar"))
+                {
+                    Setters = { new Setter(Control.TagProperty, type.Name + " bar") }
+                };
+            }
+        }
+    }
+}

+ 3 - 2
tests/Avalonia.Benchmarks/Styling/Style_Activation.cs

@@ -1,6 +1,8 @@
 using System.Runtime.CompilerServices;
 using Avalonia.Controls;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
+using Avalonia.UnitTests;
 using BenchmarkDotNet.Attributes;
 
 #nullable enable
@@ -26,8 +28,7 @@ namespace Avalonia.Benchmarks.Styling
             {
                 Setters = { new Setter(TestClass.StringProperty, "foo") }
             };
-
-            style.TryAttach(_target, null);
+            StyleHelpers.TryAttach(style, _target);
         }
 
         [Benchmark]

+ 3 - 1
tests/Avalonia.Benchmarks/Styling/Style_Apply.cs

@@ -2,7 +2,9 @@
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
 using Avalonia.Controls;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
+using Avalonia.UnitTests;
 using BenchmarkDotNet.Attributes;
 
 #nullable enable
@@ -56,7 +58,7 @@ namespace Avalonia.Benchmarks.Styling
             target.GetValueStore().BeginStyling();
 
             foreach (var style in _styles)
-                style.TryAttach(target, null);
+                StyleHelpers.TryAttach(style, target);
 
             target.GetValueStore().EndStyling();
         }

+ 1 - 1
tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs

@@ -45,7 +45,7 @@ namespace Avalonia.Benchmarks.Styling
             if ((string)_control.Tag != "TextBox")
                 throw new Exception("Invalid benchmark state");
 
-            ((IStyleable)_control).DetachStyles();
+            _control.InvalidateStyles(true);
 
             if (_control.Tag is not null)
                 throw new Exception("Invalid benchmark state");

+ 6 - 4
tests/Avalonia.Benchmarks/Styling/Style_ClassSelector.cs

@@ -1,7 +1,9 @@
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
 using Avalonia.Controls;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
+using Avalonia.UnitTests;
 using BenchmarkDotNet.Attributes;
 
 #nullable enable
@@ -35,7 +37,7 @@ namespace Avalonia.Benchmarks.Styling
             target.GetValueStore().BeginStyling();
 
             for (var i = 0; i < 50; ++i)
-                _style.TryAttach(target, null);
+                StyleHelpers.TryAttach(_style, target);
 
             target.GetValueStore().EndStyling();
         }
@@ -48,7 +50,7 @@ namespace Avalonia.Benchmarks.Styling
             target.GetValueStore().BeginStyling();
 
             for (var i = 0; i < 50; ++i)
-                _style.TryAttach(target, null);
+                StyleHelpers.TryAttach(_style, target);
 
             target.GetValueStore().EndStyling();
 
@@ -64,7 +66,7 @@ namespace Avalonia.Benchmarks.Styling
             target.GetValueStore().BeginStyling();
 
             for (var i = 0; i < 50; ++i)
-                _style.TryAttach(target, null);
+                StyleHelpers.TryAttach(_style, target);
 
             target.GetValueStore().EndStyling();
 
@@ -75,7 +77,7 @@ namespace Avalonia.Benchmarks.Styling
         {
             public static readonly StyledProperty<string?> StringProperty =
                 AvaloniaProperty.Register<TestClass, string?>("String");
-            public void DetachStyles() => InvalidateStyles();
+            public void DetachStyles() => InvalidateStyles(recurse: true);
         }
 
         private class TestClass2 : Control

+ 3 - 1
tests/Avalonia.Benchmarks/Styling/Style_NonActive.cs

@@ -1,6 +1,8 @@
 using System.Runtime.CompilerServices;
 using Avalonia.Controls;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
+using Avalonia.UnitTests;
 using BenchmarkDotNet.Attributes;
 
 #nullable enable
@@ -27,7 +29,7 @@ namespace Avalonia.Benchmarks.Styling
                 Setters = { new Setter(TestClass.StringProperty, "foo") }
             };
 
-            style.TryAttach(_target, null);
+            StyleHelpers.TryAttach(style, _target);
             _target.SetValue(TestClass.StringProperty, "foo");
             _target.SetValue(TestClass.Struct1Property, new(1));
             _target.SetValue(TestClass.Struct2Property, new(1));

+ 2 - 2
tests/Avalonia.Controls.UnitTests/ContentControlTests.cs

@@ -292,13 +292,13 @@ namespace Avalonia.Controls.UnitTests
             // as they are in Avalonia.Markup.Xaml.
             DelayedBinding.Add(presenter, ContentPresenter.ContentProperty, new Binding("Content")
             {
-                Priority = BindingPriority.TemplatedParent,
+                Priority = BindingPriority.Template,
                 RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
             });
 
             DelayedBinding.Add(presenter, ContentPresenter.ContentTemplateProperty, new Binding("ContentTemplate")
             {
-                Priority = BindingPriority.TemplatedParent,
+                Priority = BindingPriority.Template,
                 RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
             });
 

+ 2 - 2
tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs

@@ -895,14 +895,14 @@ namespace Avalonia.Controls.UnitTests
                     {
                         Path = nameof(TextPresenter.Text),
                         Mode = BindingMode.TwoWay,
-                        Priority = BindingPriority.TemplatedParent,
+                        Priority = BindingPriority.Template,
                         RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
                     },
                     [!!TextPresenter.CaretIndexProperty] = new Binding
                     {
                         Path = nameof(TextPresenter.CaretIndex),
                         Mode = BindingMode.TwoWay,
-                        Priority = BindingPriority.TemplatedParent,
+                        Priority = BindingPriority.Template,
                         RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
                     }
                 }.RegisterInNameScope(scope));

+ 2 - 2
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@@ -891,14 +891,14 @@ namespace Avalonia.Controls.UnitTests
                     {
                         Path = nameof(TextPresenter.Text),
                         Mode = BindingMode.TwoWay,
-                        Priority = BindingPriority.TemplatedParent,
+                        Priority = BindingPriority.Template,
                         RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
                     },
                     [!!TextPresenter.CaretIndexProperty] = new Binding
                     {
                         Path = nameof(TextPresenter.CaretIndex),
                         Mode = BindingMode.TwoWay,
-                        Priority = BindingPriority.TemplatedParent,
+                        Priority = BindingPriority.Template,
                         RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
                     }
                 }.RegisterInNameScope(scope));

+ 1 - 1
tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs

@@ -100,7 +100,7 @@ namespace Avalonia.Controls.UnitTests
                     {
                         Path = "Text",
                         Mode = BindingMode.TwoWay,
-                        Priority = BindingPriority.TemplatedParent,
+                        Priority = BindingPriority.Template,
                         RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
                     },
                 }.RegisterInNameScope(scope));

+ 1 - 1
tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs

@@ -54,7 +54,7 @@ namespace Avalonia.Markup.UnitTests.Data
                 DataContext = new Class1(),
             };
 
-            var target = new Binding(nameof(Class1.Foo)) { Priority = BindingPriority.TemplatedParent };
+            var target = new Binding(nameof(Class1.Foo)) { Priority = BindingPriority.Template };
             var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true);
             var subject = (BindingExpression)instanced.Subject;
             object result = null;

+ 2 - 1
tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs

@@ -2,6 +2,7 @@ using System.Linq;
 using System.Reactive.Linq;
 using Avalonia.Controls;
 using Avalonia.Data;
+using Avalonia.PropertyStore;
 using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Xunit;
@@ -54,7 +55,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
                     }
                 };
 
-                style.TryAttach(control, control);
+                StyleHelpers.TryAttach(style, control);
                 Assert.Equal("foo", control.Text);
 
                 control.Text = "bar";

+ 6 - 6
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs

@@ -77,7 +77,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal(Brushes.Red, presenter.Background);
 
                 var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty);
-                Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority);
+                Assert.Equal(BindingPriority.Template, diagnostic.Priority);
             }
         }
 
@@ -111,7 +111,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal(Brushes.Red, presenter.Background);
 
                 var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty);
-                Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority);
+                Assert.Equal(BindingPriority.Template, diagnostic.Priority);
             }
         }
 
@@ -142,7 +142,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal(Dock.Top, DockPanel.GetDock(presenter));
 
                 var diagnostic = presenter.GetDiagnostic(DockPanel.DockProperty);
-                Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority);
+                Assert.Equal(BindingPriority.Template, diagnostic.Priority);
             }
         }
 
@@ -176,7 +176,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal(Brushes.Red, presenter.Background);
 
                 var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty);
-                Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority);
+                Assert.Equal(BindingPriority.Template, diagnostic.Priority);
             }
         }
 
@@ -210,7 +210,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal(Brushes.Red, presenter.Background);
 
                 var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty);
-                Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority);
+                Assert.Equal(BindingPriority.Template, diagnostic.Priority);
             }
         }
 
@@ -241,7 +241,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Equal("Foo", presenter.Content);
 
                 var diagnostic = presenter.GetDiagnostic(ContentPresenter.ContentProperty);
-                Assert.Equal(BindingPriority.TemplatedParent, diagnostic.Priority);
+                Assert.Equal(BindingPriority.Template, diagnostic.Priority);
             }
         }
 

+ 14 - 0
tests/Avalonia.UnitTests/StyleHelpers.cs

@@ -0,0 +1,14 @@
+using Avalonia.Styling;
+
+#nullable enable
+
+namespace Avalonia.UnitTests
+{
+    public static class StyleHelpers
+    {
+        public static SelectorMatchResult TryAttach(Style style, StyledElement element, object? host = null)
+        {
+            return style.TryAttach(element, host ?? element, PropertyStore.FrameType.Style);
+        }
+    }
+}