diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index f1fcd8035a..4c3c211ca5 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -14,22 +14,6 @@ - - - - - - A control for displaying and interacting with a data source. @@ -45,8 +29,7 @@ + RowBackground="#1000"> @@ -59,6 +42,24 @@ IsVisible="{Binding #ShowGDP.IsChecked}" x:DataType="local:Country" /> + + + + + + + + + + + + + + @@ -71,6 +72,20 @@ + + + + + + + + diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 8790991182..d858e30212 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -610,8 +610,7 @@ namespace Avalonia.PropertyStore private int InsertFrame(ValueFrame frame) { - // Uncomment this line when #8549 is fixed. - //Debug.Assert(!_frames.Contains(frame)); + Debug.Assert(!_frames.Contains(frame)); var index = BinarySearchFrame(frame.Priority); _frames.Insert(index, frame); diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index bba9685ed8..c72f398fd9 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -344,29 +344,30 @@ namespace Avalonia /// Applies styling to the control if the control is initialized and styling is not /// already applied. /// + /// + /// The styling system will automatically apply styling when required, so it should not + /// usually be necessary to call this method manually. + /// /// /// A value indicating whether styling is now applied to the control. /// - protected bool ApplyStyling() + public bool ApplyStyling() { if (_initCount == 0 && !_styled) { - var styler = AvaloniaLocator.Current.GetService(); var hasPromotedTheme = _hasPromotedTheme; - if (styler is object) - { - GetValueStore().BeginStyling(); + GetValueStore().BeginStyling(); - try - { - styler.ApplyStyles(this); - } - finally - { - _styled = true; - GetValueStore().EndStyling(); - } + try + { + ApplyControlTheme(); + ApplyStyles(this); + } + finally + { + _styled = true; + GetValueStore().EndStyling(); } if (hasPromotedTheme) @@ -505,31 +506,6 @@ namespace Avalonia }; } - ControlTheme? IStyleable.GetEffectiveTheme() - { - var theme = Theme; - - // Explitly set Theme property takes precedence. - if (theme is not null) - return theme; - - // If the Theme property is not set, try to find a ControlTheme resource with our StyleKey. - if (_implicitTheme is null) - { - var key = ((IStyleable)this).StyleKey; - - if (this.TryFindResource(key, out var value) && value is ControlTheme t) - _implicitTheme = t; - else - _implicitTheme = s_invalidTheme; - } - - if (_implicitTheme != s_invalidTheme) - return _implicitTheme; - - return null; - } - void IStyleable.DetachStyles() => DetachStyles(); void IStyleHost.StylesAdded(IReadOnlyList styles) @@ -666,6 +642,31 @@ namespace Avalonia { } + internal ControlTheme? GetEffectiveTheme() + { + var theme = Theme; + + // Explitly set Theme property takes precedence. + if (theme is not null) + return theme; + + // If the Theme property is not set, try to find a ControlTheme resource with our StyleKey. + if (_implicitTheme is null) + { + var key = ((IStyleable)this).StyleKey; + + if (this.TryFindResource(key, out var value) && value is ControlTheme t) + _implicitTheme = t; + else + _implicitTheme = s_invalidTheme; + } + + if (_implicitTheme != s_invalidTheme) + return _implicitTheme; + + return null; + } + private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) { if (o is StyledElement element) @@ -730,6 +731,56 @@ namespace Avalonia } } + private void ApplyControlTheme() + { + var theme = GetEffectiveTheme(); + + if (theme is not null) + ApplyControlTheme(theme); + + if (TemplatedParent is StyledElement styleableParent && + styleableParent.GetEffectiveTheme() is { } parentTheme) + { + ApplyControlTheme(parentTheme); + } + } + + private void ApplyControlTheme(ControlTheme theme) + { + if (theme.BasedOn is ControlTheme basedOn) + ApplyControlTheme(basedOn); + + theme.TryAttach(this, null); + + if (theme.HasChildren) + { + foreach (var child in theme.Children) + ApplyStyle(child, null); + } + } + + private void ApplyStyles(IStyleHost host) + { + var parent = host.StylingParent; + if (parent != null) + ApplyStyles(parent); + + if (host.IsStylesInitialized) + { + foreach (var style in host.Styles) + ApplyStyle(style, host); + } + } + + private void ApplyStyle(IStyle style, IStyleHost? host) + { + if (style is Style s) + s.TryAttach(this, host); + + foreach (var child in style.Children) + ApplyStyle(child, host); + } + private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) { if (this.GetLogicalParent() == null && !(this is ILogicalRoot)) diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index 46a3267f70..2971703c95 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -29,34 +29,27 @@ namespace Avalonia.Styling /// public ControlTheme? BasedOn { get; set; } - public override SelectorMatchResult TryAttach(IStyleable target, object? host) + public override string ToString() => TargetType?.Name ?? "ControlTheme"; + + internal override void SetParent(StyleBase? parent) + { + throw new InvalidOperationException("ControlThemes cannot be added as a nested style."); + } + + internal override SelectorMatchResult TryAttach(IStyleable target, object? host) { _ = target ?? throw new ArgumentNullException(nameof(target)); if (TargetType is null) throw new InvalidOperationException("ControlTheme has no TargetType."); - var result = BasedOn?.TryAttach(target, host) ?? SelectorMatchResult.NeverThisType; - if (HasSettersOrAnimations && TargetType.IsAssignableFrom(target.StyleKey)) { Attach(target, null); - result = SelectorMatchResult.AlwaysThisType; + return SelectorMatchResult.AlwaysThisType; } - var childResult = TryAttachChildren(target, host); - - if (childResult > result) - result = childResult; - - return result; - } - - public override string ToString() => TargetType?.Name ?? "ControlTheme"; - - internal override void SetParent(StyleBase? parent) - { - throw new InvalidOperationException("ControlThemes cannot be added as a nested style."); + return SelectorMatchResult.NeverThisType; } } } diff --git a/src/Avalonia.Base/Styling/IStyle.cs b/src/Avalonia.Base/Styling/IStyle.cs index 417739fb28..2dbaf963ee 100644 --- a/src/Avalonia.Base/Styling/IStyle.cs +++ b/src/Avalonia.Base/Styling/IStyle.cs @@ -14,15 +14,5 @@ namespace Avalonia.Styling /// Gets a collection of child styles. /// IReadOnlyList Children { get; } - - /// - /// Attaches the style and any child styles to a control if the style's selector matches. - /// - /// The control to attach to. - /// The element that hosts the style. - /// - /// A describing how the style matches the control. - /// - SelectorMatchResult TryAttach(IStyleable target, object? host); } } diff --git a/src/Avalonia.Base/Styling/IStyleable.cs b/src/Avalonia.Base/Styling/IStyleable.cs index e94fc5c4e6..dcc3988280 100644 --- a/src/Avalonia.Base/Styling/IStyleable.cs +++ b/src/Avalonia.Base/Styling/IStyleable.cs @@ -25,11 +25,6 @@ namespace Avalonia.Styling /// ITemplatedControl? TemplatedParent { get; } - /// - /// Gets the effective theme for the control as used by the syling system. - /// - ControlTheme? GetEffectiveTheme(); - void DetachStyles(); } } diff --git a/src/Avalonia.Base/Styling/IStyler.cs b/src/Avalonia.Base/Styling/IStyler.cs deleted file mode 100644 index d6477d169e..0000000000 --- a/src/Avalonia.Base/Styling/IStyler.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace Avalonia.Styling -{ - public interface IStyler - { - void ApplyStyles(IStyleable control); - } -} diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index 913c437bc4..aad91824d3 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.PropertyStore; namespace Avalonia.Styling { @@ -35,35 +34,6 @@ namespace Avalonia.Styling set => _selector = ValidateSelector(value); } - public override SelectorMatchResult TryAttach(IStyleable target, object? host) - { - _ = target ?? throw new ArgumentNullException(nameof(target)); - - var result = SelectorMatchResult.NeverThisType; - - if (HasSettersOrAnimations) - { - var match = Selector?.Match(target, Parent, true) ?? - (target == host ? - SelectorMatch.AlwaysThisInstance : - SelectorMatch.NeverThisInstance); - - if (match.IsMatch) - { - Attach(target, match.Activator); - } - - result = match.Result; - } - - var childResult = TryAttachChildren(target, host); - - if (childResult > result) - result = childResult; - - return result; - } - /// /// Returns a string representation of the style. /// @@ -88,6 +58,30 @@ namespace Avalonia.Styling base.SetParent(parent); } + internal override SelectorMatchResult TryAttach(IStyleable target, object? host) + { + _ = target ?? throw new ArgumentNullException(nameof(target)); + + var result = SelectorMatchResult.NeverThisType; + + if (HasSettersOrAnimations) + { + var match = Selector?.Match(target, Parent, true) ?? + (target == host ? + SelectorMatch.AlwaysThisInstance : + SelectorMatch.NeverThisInstance); + + if (match.IsMatch) + { + Attach(target, match.Activator); + } + + result = match.Result; + } + + return result; + } + private static Selector? ValidateSelector(Selector? selector) { if (selector is TemplateSelector) diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs index c914fbf8cc..dba80df2e5 100644 --- a/src/Avalonia.Base/Styling/StyleBase.cs +++ b/src/Avalonia.Base/Styling/StyleBase.cs @@ -18,7 +18,6 @@ namespace Avalonia.Styling private IResourceDictionary? _resources; private List? _setters; private List? _animations; - private StyleCache? _childCache; private StyleInstance? _sharedInstance; public IList Children => _children ??= new(this); @@ -67,6 +66,7 @@ namespace Avalonia.Styling bool IResourceNode.HasResources => _resources?.Count > 0; IReadOnlyList IStyle.Children => (IReadOnlyList?)_children ?? Array.Empty(); + internal bool HasChildren => _children?.Count > 0; internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0; public void Add(ISetter setter) => Setters.Add(setter); @@ -74,14 +74,26 @@ namespace Avalonia.Styling public event EventHandler? OwnerChanged; - public abstract SelectorMatchResult TryAttach(IStyleable target, object? host); - public bool TryGetResource(object key, out object? result) { - result = null; - return _resources?.TryGetResource(key, out result) ?? false; + if (_resources is not null && _resources.TryGetResource(key, out result)) + return true; + + if (_children is not null) + { + for (var i = 0; i < _children.Count; ++i) + { + if (_children[i].TryGetResource(key, out result)) + return true; + } + } + + result= null; + return false; } + internal abstract SelectorMatchResult TryAttach(IStyleable target, object? host); + internal ValueFrame Attach(IStyleable target, IStyleActivator? activator) { if (target is not AvaloniaObject ao) @@ -124,14 +136,6 @@ namespace Avalonia.Styling return instance; } - internal SelectorMatchResult TryAttachChildren(IStyleable target, object? host) - { - if (_children is null || _children.Count == 0) - return SelectorMatchResult.NeverThisType; - _childCache ??= new StyleCache(); - return _childCache.TryAttach(_children, target, host); - } - internal virtual void SetParent(StyleBase? parent) => Parent = parent; void IResourceProvider.AddOwner(IResourceHost owner) diff --git a/src/Avalonia.Base/Styling/StyleCache.cs b/src/Avalonia.Base/Styling/StyleCache.cs deleted file mode 100644 index 81196f6a27..0000000000 --- a/src/Avalonia.Base/Styling/StyleCache.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Avalonia.Styling -{ - /// - /// Simple cache for improving performance of applying styles. - /// - /// - /// Maps to a list of styles that are known be be possible - /// matches. - /// - internal class StyleCache : Dictionary?> - { - public SelectorMatchResult TryAttach(IList styles, IStyleable target, object? host) - { - if (TryGetValue(target.StyleKey, out var cached)) - { - if (cached is object) - { - var result = SelectorMatchResult.NeverThisType; - - foreach (var style in cached) - { - var childResult = style.TryAttach(target, host); - if (childResult > result) - result = childResult; - } - - return result; - } - else - { - return SelectorMatchResult.NeverThisType; - } - } - else - { - List? matches = null; - - foreach (var child in styles) - { - if (child.TryAttach(target, host) != SelectorMatchResult.NeverThisType) - { - matches ??= new List(); - matches.Add(child); - } - } - - Add(target.StyleKey, matches); - - return matches is null ? - SelectorMatchResult.NeverThisType : - SelectorMatchResult.AlwaysThisType; - } - } - } -} diff --git a/src/Avalonia.Base/Styling/Styler.cs b/src/Avalonia.Base/Styling/Styler.cs deleted file mode 100644 index ad5c1cd102..0000000000 --- a/src/Avalonia.Base/Styling/Styler.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace Avalonia.Styling -{ - public class Styler : IStyler - { - public void ApplyStyles(IStyleable target) - { - _ = target ?? throw new ArgumentNullException(nameof(target)); - - // Apply the control theme. - target.GetEffectiveTheme()?.TryAttach(target, target); - - // If the control has a themed templated parent then apply the styles from the - // templated parent theme. - if (target.TemplatedParent is IStyleable styleableParent) - styleableParent.GetEffectiveTheme()?.TryAttach(target, styleableParent); - - // Apply styles from the rest of the tree. - if (target is IStyleHost styleHost) - ApplyStyles(target, styleHost); - } - - private void ApplyStyles(IStyleable target, IStyleHost host) - { - var parent = host.StylingParent; - - if (parent != null) - ApplyStyles(target, parent); - - if (host.IsStylesInitialized) - host.Styles.TryAttach(target, host); - } - } -} diff --git a/src/Avalonia.Base/Styling/Styles.cs b/src/Avalonia.Base/Styling/Styles.cs index c213475bb7..76271b9748 100644 --- a/src/Avalonia.Base/Styling/Styles.cs +++ b/src/Avalonia.Base/Styling/Styles.cs @@ -5,8 +5,6 @@ using System.Collections.Specialized; using Avalonia.Collections; using Avalonia.Controls; -#nullable enable - namespace Avalonia.Styling { /// @@ -20,7 +18,6 @@ namespace Avalonia.Styling private readonly AvaloniaList _styles = new(); private IResourceHost? _owner; private IResourceDictionary? _resources; - private StyleCache? _cache; public Styles() { @@ -116,12 +113,6 @@ namespace Avalonia.Styling set => _styles[index] = value; } - public SelectorMatchResult TryAttach(IStyleable target, object? host) - { - _cache ??= new StyleCache(); - return _cache.TryAttach(this, target, host); - } - /// public bool TryGetResource(object key, out object? value) { @@ -234,6 +225,22 @@ namespace Avalonia.Styling } } + internal SelectorMatchResult TryAttach(IStyleable target, object? host) + { + var result = SelectorMatchResult.NeverThisType; + + foreach (var s in this) + { + if (s is not Style style) + continue; + var r = style.TryAttach(target, host); + if (r > result) + result = r; + } + + return result; + } + private static IReadOnlyList ToReadOnlyList(ICollection list) { if (list is IReadOnlyList readOnlyList) @@ -246,7 +253,7 @@ namespace Avalonia.Styling return result; } - private static void InternalAdd(IList items, IResourceHost? owner, ref StyleCache? cache) + private static void InternalAdd(IList items, IResourceHost? owner) { if (owner is not null) { @@ -260,14 +267,9 @@ namespace Avalonia.Styling (owner as IStyleHost)?.StylesAdded(ToReadOnlyList(items)); } - - if (items.Count > 0) - { - cache = null; - } } - private static void InternalRemove(IList items, IResourceHost? owner, ref StyleCache? cache) + private static void InternalRemove(IList items, IResourceHost? owner) { if (owner is not null) { @@ -281,11 +283,6 @@ namespace Avalonia.Styling (owner as IStyleHost)?.StylesRemoved(ToReadOnlyList(items)); } - - if (items.Count > 0) - { - cache = null; - } } private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -300,14 +297,14 @@ namespace Avalonia.Styling switch (e.Action) { case NotifyCollectionChangedAction.Add: - InternalAdd(e.NewItems!, currentOwner, ref _cache); + InternalAdd(e.NewItems!, currentOwner); break; case NotifyCollectionChangedAction.Remove: - InternalRemove(e.OldItems!, currentOwner, ref _cache); + InternalRemove(e.OldItems!, currentOwner); break; case NotifyCollectionChangedAction.Replace: - InternalRemove(e.OldItems!, currentOwner, ref _cache); - InternalAdd(e.NewItems!, currentOwner, ref _cache); + InternalRemove(e.OldItems!, currentOwner); + InternalAdd(e.NewItems!, currentOwner); break; } diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index c6cc9bf278..a9f2e889b9 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/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(nameof(ColumnWidth), defaultValue: DataGridLength.Auto); /// - /// Gets or sets the standard width or automatic sizing mode of columns in the control. + /// Identifies the dependency property. /// - public DataGridLength ColumnWidth + public static readonly StyledProperty RowThemeProperty = + AvaloniaProperty.Register(nameof(RowTheme)); + + /// + /// Gets or sets the theme applied to all rows. + /// + public ControlTheme RowTheme { - get { return GetValue(ColumnWidthProperty); } - set { SetValue(ColumnWidthProperty, value); } + get { return GetValue(RowThemeProperty); } + set { SetValue(RowThemeProperty, value); } } - public static readonly StyledProperty AlternatingRowBackgroundProperty = - AvaloniaProperty.Register(nameof(AlternatingRowBackground)); + /// + /// Identifies the dependency property. + /// + public static readonly StyledProperty CellThemeProperty = + AvaloniaProperty.Register(nameof(CellTheme)); /// - /// Gets or sets the that is used to paint the background of odd-numbered rows. + /// Gets or sets the theme applied to all cells. /// - /// - /// The brush that is used to paint the background of odd-numbered rows. The default is a - /// with a - /// value of white (ARGB value #00FFFFFF). - /// - public IBrush AlternatingRowBackground + public ControlTheme CellTheme + { + get { return GetValue(CellThemeProperty); } + set { SetValue(CellThemeProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly StyledProperty ColumnHeaderThemeProperty = + AvaloniaProperty.Register(nameof(ColumnHeaderTheme)); + + /// + /// Gets or sets the theme applied to all column headers. + /// + public ControlTheme ColumnHeaderTheme + { + get { return GetValue(ColumnHeaderThemeProperty); } + set { SetValue(ColumnHeaderThemeProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly StyledProperty RowGroupThemeProperty = + AvaloniaProperty.Register(nameof(RowGroupTheme)); + + /// + /// Gets or sets the theme applied to all row groups. + /// + public ControlTheme RowGroupTheme { - get { return GetValue(AlternatingRowBackgroundProperty); } - set { SetValue(AlternatingRowBackgroundProperty, value); } + get { return GetValue(RowGroupThemeProperty); } + set { SetValue(RowGroupThemeProperty, value); } + } + + /// + /// Gets or sets the standard width or automatic sizing mode of columns in the control. + /// + public DataGridLength ColumnWidth + { + get { return GetValue(ColumnWidthProperty); } + set { SetValue(ColumnWidthProperty, value); } } public static readonly StyledProperty FrozenColumnCountProperty = @@ -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; } - + /// protected override void OnDataContextBeginUpdate() { @@ -2183,7 +2227,7 @@ namespace Avalonia.Controls NotifyDataContextPropertyForAllRowCells(GetAllRows(), false); } - + /// /// Raises the BeginningEdit event. /// @@ -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) diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index fbdb979e24..191c3fc5b1 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -231,7 +231,7 @@ namespace Avalonia.Controls } /// - /// 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. /// /// @@ -260,15 +260,15 @@ namespace Avalonia.Controls /// 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 /// /// /// When setting this property, the specified value is less than -1 or equal to . - /// + /// /// -or- - /// + /// /// When setting this property on a column in a , the specified value is less than zero or greater than or equal to the number of columns in the . /// /// /// When setting this property, the is already making adjustments. For example, this exception is thrown when you attempt to set in a 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. /// public int DisplayIndex @@ -401,7 +401,7 @@ namespace Avalonia.Controls } } } - + /// /// Backing field for CellTheme property. /// @@ -412,7 +412,7 @@ namespace Avalonia.Controls (o, v) => o.CellTheme = v); /// - /// Gets or sets the cell theme. + /// Gets or sets the cell theme. /// public ControlTheme CellTheme { @@ -430,14 +430,14 @@ namespace Avalonia.Controls (o, v) => o.Header = v); /// - /// Gets or sets the content + /// Gets or sets the content /// public object Header { get { return _header; } set { SetAndRaise(HeaderProperty, ref _header, value); } } - + /// /// Backing field for Header property /// @@ -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); /// - /// 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 /// property value. /// /// @@ -765,7 +765,7 @@ namespace Avalonia.Controls protected abstract IControl GenerateElement(DataGridCell cell, object dataItem); /// - /// 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. /// /// Indicates which property changed and caused this call @@ -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; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index d3bd968d62..740e3516f6 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -76,7 +76,7 @@ namespace Avalonia.Controls } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// //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 } /// - /// 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)) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index 1559763a1b..cd22934ac0 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -89,7 +89,7 @@ namespace Avalonia.Controls o => o.IsValid); /// - /// 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. /// public bool IsValid { @@ -130,7 +130,7 @@ namespace Avalonia.Controls } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// 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 } /// - /// Measures the children of a to + /// Measures the children of a to /// prepare for arranging them during the pass. /// /// @@ -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 - } diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index 69e6766bfd..c746b19cc7 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(PropertyName)); /// - /// Gets or sets the name of the property that this row is bound to. + /// Gets or sets the name of the property that this row is bound to. /// public string PropertyName { @@ -85,8 +85,8 @@ namespace Avalonia.Controls } /// - /// Gets or sets a value that indicates the amount that the - /// children of the are indented. + /// Gets or sets a value that indicates the amount that the + /// children of the are indented. /// 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) diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index f3afe2c42d..17a7eab2e0 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/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 } /// - /// 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. /// 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 /// /// 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. /// 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 diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index 3d98722da5..a3c0ed1d0c 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -232,7 +232,7 @@ - + @@ -337,10 +337,6 @@ - - -