diff --git a/src/AtomUI.Controls/DatePicker/RangeDatePickerFlyout.cs b/src/AtomUI.Controls/DatePicker/RangeDatePickerFlyout.cs index fcd5d0594..0acfe8875 100644 --- a/src/AtomUI.Controls/DatePicker/RangeDatePickerFlyout.cs +++ b/src/AtomUI.Controls/DatePicker/RangeDatePickerFlyout.cs @@ -1,5 +1,4 @@ using AtomUI.Data; -using Avalonia; using Avalonia.Controls; namespace AtomUI.Controls; diff --git a/src/AtomUI.Controls/Flyouts/Flyout.cs b/src/AtomUI.Controls/Flyouts/Flyout.cs index d51cac049..35e73a3e2 100644 --- a/src/AtomUI.Controls/Flyouts/Flyout.cs +++ b/src/AtomUI.Controls/Flyouts/Flyout.cs @@ -1,6 +1,5 @@ using System.ComponentModel; using System.Reactive.Disposables; -using System.Runtime.CompilerServices; using AtomUI.Data; using AtomUI.Theme.Data; using AtomUI.Theme.Styling; @@ -126,14 +125,6 @@ public class Flyout : PopupFlyoutBase IsShowArrowProperty.OverrideDefaultValue(false); } - public Flyout() - { - TokenResourceBinder.CreateGlobalTokenBinding(this, MotionDurationTokenProperty, - SharedTokenKey.MotionDurationMid); - TokenResourceBinder.CreateGlobalTokenBinding(this, MaskShadowsProperty, - SharedTokenKey.BoxShadowsSecondary); - } - private void HandlePopupPropertyChanged(AvaloniaPropertyChangedEventArgs args) { SetupArrowPosition(Popup); @@ -210,11 +201,15 @@ public class Flyout : PopupFlyoutBase { CompositeDisposable.Add(PopupControl.IsFlippedProperty.Changed.Subscribe(HandlePopupPropertyChanged)); } + + CompositeDisposable.Add(TokenResourceBinder.CreateGlobalTokenBinding(this, MotionDurationTokenProperty, + SharedTokenKey.MotionDurationMid)); + CompositeDisposable.Add(TokenResourceBinder.CreateGlobalTokenBinding(this, MaskShadowsProperty, + SharedTokenKey.BoxShadowsSecondary)); } protected override void OnClosed() { - base.OnClosed(); CompositeDisposable?.Dispose(); } @@ -348,10 +343,7 @@ public class Flyout : PopupFlyoutBase IsOpen = true; Dispatcher.UIThread.InvokeAsync(() => { - Popup.MotionAwareOpen(() => - { - HandlePopupOpened(placementTarget); - }); + Popup.MotionAwareOpen(() => { HandlePopupOpened(placementTarget); }); }); return true; } diff --git a/src/AtomUI.Controls/Internal/InfoPickerInput/InfoPickerInputTheme.cs b/src/AtomUI.Controls/Internal/InfoPickerInput/InfoPickerInputTheme.cs index 396cf2f5c..e37c7fbce 100644 --- a/src/AtomUI.Controls/Internal/InfoPickerInput/InfoPickerInputTheme.cs +++ b/src/AtomUI.Controls/Internal/InfoPickerInput/InfoPickerInputTheme.cs @@ -122,9 +122,8 @@ internal class InfoPickerInputTheme : BaseControlTheme IsEnableClearButton = false, IsEnableRevealButton = false, }; - - BindUtils.RelayBind(this, DataValidationErrors.ErrorsProperty, pickerTextBox, - DataValidationErrors.ErrorsProperty); + + CreateTemplateParentBinding(pickerTextBox, DataValidationErrors.ErrorsProperty, DataValidationErrors.ErrorsProperty); CreateTemplateParentBinding(pickerTextBox, TextBox.ForegroundProperty, InfoPickerInput.InputTextBrushProperty); CreateTemplateParentBinding(pickerTextBox, TextBox.SizeTypeProperty, InfoPickerInput.SizeTypeProperty); CreateTemplateParentBinding(pickerTextBox, TextBox.IsReadOnlyProperty, diff --git a/src/AtomUI.Controls/NavMenu/NavMenu.cs b/src/AtomUI.Controls/NavMenu/NavMenu.cs index 5753c7bd2..277f79ddd 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenu.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenu.cs @@ -1,13 +1,13 @@ -using AtomUI.Data; +using System.Reactive.Disposables; +using AtomUI.Data; +using AtomUI.Theme; using AtomUI.Theme.Data; using AtomUI.Theme.Styling; -using AtomUI.Utils; using Avalonia; using Avalonia.Automation; using Avalonia.Automation.Peers; using Avalonia.Controls; using Avalonia.Controls.Metadata; -using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; @@ -29,14 +29,15 @@ public class NavMenuItemClickEventArgs : RoutedEventArgs } [PseudoClasses(InlineModePC, HorizontalModePC, VerticalModePC)] -public class NavMenu : NavMenuBase +public class NavMenu : NavMenuBase, + ITokenResourceConsumer { public const string InlineModePC = ":inline-mode"; public const string HorizontalModePC = ":horizontal-mode"; public const string VerticalModePC = ":vertical-mode"; public const string DarkStylePC = ":dark"; public const string LightStylePC = ":light"; - + #region 公共属性定义 /// @@ -44,41 +45,41 @@ public class NavMenu : NavMenuBase /// public static readonly StyledProperty ModeProperty = AvaloniaProperty.Register(nameof(Mode), NavMenuMode.Horizontal); - + public static readonly StyledProperty IsDarkStyleProperty = AvaloniaProperty.Register(nameof(IsDarkStyle), false); - + public static readonly StyledProperty ActiveBarWidthProperty = AvaloniaProperty.Register(nameof(ActiveBarWidth), 1.0d, coerce: (o, v) => Math.Max(Math.Min(v, 1.0), 0.0)); - + public static readonly StyledProperty ActiveBarHeightProperty = AvaloniaProperty.Register(nameof(ActiveBarHeight)); - + public NavMenuMode Mode { get => GetValue(ModeProperty); set => SetValue(ModeProperty, value); } - + public bool IsDarkStyle { get => GetValue(IsDarkStyleProperty); set => SetValue(IsDarkStyleProperty, value); } - + public double ActiveBarWidth { get => GetValue(ActiveBarWidthProperty); set => SetValue(ActiveBarWidthProperty, value); } - + public double ActiveBarHeight { get => GetValue(ActiveBarHeightProperty); set => SetValue(ActiveBarHeightProperty, value); } - + #endregion #region 公共事件定义 @@ -87,7 +88,7 @@ public class NavMenu : NavMenuBase RoutedEvent.Register( nameof(NavMenuItemClick), RoutingStrategies.Bubble); - + public event EventHandler? NavMenuItemClick { add => AddHandler(NavMenuItemClickEvent, value); @@ -100,15 +101,15 @@ public class NavMenu : NavMenuBase internal static readonly StyledProperty HorizontalBorderThicknessProperty = AvaloniaProperty.Register(nameof(HorizontalBorderThickness)); - + public double HorizontalBorderThickness { get => GetValue(HorizontalBorderThicknessProperty); set => SetValue(HorizontalBorderThicknessProperty, value); } - + #endregion - + private static readonly FuncTemplate DefaultPanel = new(() => new StackPanel { Orientation = Orientation.Vertical }); @@ -122,7 +123,9 @@ public class NavMenu : NavMenuBase AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.Menu); } - private bool _initialized; + private CompositeDisposable? _tokenBindingsDisposable; + + CompositeDisposable? ITokenResourceConsumer.TokenBindingsDisposable => _tokenBindingsDisposable; public NavMenu() { @@ -176,6 +179,7 @@ public class NavMenu : NavMenuBase { UpdatePseudoClasses(); } + if (change.Property == ModeProperty) { if (VisualRoot is not null) @@ -185,16 +189,18 @@ public class NavMenu : NavMenuBase } } } - + private void SetupControlTheme() { if (Mode == NavMenuMode.Horizontal) { - TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, HorizontalNavMenuTheme.ID); + this.AddTokenBindingDisposable( + TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, HorizontalNavMenuTheme.ID)); } else { - TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, VerticalNavMenuTheme.ID); + this.AddTokenBindingDisposable( + TokenResourceBinder.CreateTokenBinding(this, ThemeProperty, VerticalNavMenuTheme.ID)); } } @@ -221,15 +227,15 @@ public class NavMenu : NavMenuBase if (Mode == NavMenuMode.Horizontal) { BindUtils.RelayBind(this, ActiveBarHeightProperty, navMenuItem, NavMenuItem.ActiveBarHeightProperty); - BindUtils.RelayBind(this, ActiveBarWidthProperty, navMenuItem, NavMenuItem.ActiveBarWidthProperty); + BindUtils.RelayBind(this, ActiveBarWidthProperty, navMenuItem, NavMenuItem.ActiveBarWidthProperty); } + BindUtils.RelayBind(this, ModeProperty, navMenuItem, NavMenuItem.ModeProperty); BindUtils.RelayBind(this, IsDarkStyleProperty, navMenuItem, NavMenuItem.IsDarkStyleProperty); BindUtils.RelayBind(this, IsMotionEnabledProperty, navMenuItem, NavMenuItem.IsMotionEnabledProperty); } - } - + private void UpdatePseudoClasses() { PseudoClasses.Set(HorizontalModePC, Mode == NavMenuMode.Horizontal); @@ -238,47 +244,52 @@ public class NavMenu : NavMenuBase PseudoClasses.Set(DarkStylePC, IsDarkStyle); PseudoClasses.Set(LightStylePC, !IsDarkStyle); } - + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); - if (!_initialized) - { - SetupControlTheme(); - SetupItemContainerTheme(); - SetupInteractionHandler(); - _initialized = true; - } + _tokenBindingsDisposable ??= new CompositeDisposable(); + + this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, HorizontalBorderThicknessProperty, + SharedTokenKey.LineWidth, + BindingPriority.Template, + new RenderScaleAwareDoubleConfigure(this))); + + this.AddTokenBindingDisposable( + TokenResourceBinder.CreateTokenBinding(this, ActiveBarWidthProperty, NavMenuTokenKey.ActiveBarWidth)); + this.AddTokenBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, ActiveBarHeightProperty, + NavMenuTokenKey.ActiveBarHeight)); + + SetupControlTheme(); + SetupItemContainerTheme(); + SetupInteractionHandler(); + } + + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + base.OnDetachedFromLogicalTree(e); + this.DisposeTokenBindings(); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); InteractionHandler?.Attach(this); - TokenResourceBinder.CreateTokenBinding(this, HorizontalBorderThicknessProperty, SharedTokenKey.LineWidth, - BindingPriority.Template, - new RenderScaleAwareDoubleConfigure(this)); } - + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); InteractionHandler?.Detach(this); } - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - TokenResourceBinder.CreateTokenBinding(this, ActiveBarWidthProperty, NavMenuTokenKey.ActiveBarWidth); - TokenResourceBinder.CreateTokenBinding(this, ActiveBarHeightProperty, NavMenuTokenKey.ActiveBarHeight); - } - private void SetupInteractionHandler(bool needMount = false) { if (needMount) { InteractionHandler?.Detach(this); } + if (Mode == NavMenuMode.Inline) { InteractionHandler = new InlineNavMenuInteractionHandler(); @@ -287,6 +298,7 @@ public class NavMenu : NavMenuBase { InteractionHandler = new DefaultNavMenuInteractionHandler(); } + if (needMount) { InteractionHandler?.Attach(this); @@ -297,7 +309,7 @@ public class NavMenu : NavMenuBase { if (ItemContainerTheme is null || force) { - string resourceKey; + string resourceKey; if (Mode == NavMenuMode.Vertical) { resourceKey = VerticalNavMenuItemTheme.ID; @@ -310,7 +322,9 @@ public class NavMenu : NavMenuBase { resourceKey = TopLevelHorizontalNavMenuItemTheme.ID; } - TokenResourceBinder.CreateGlobalResourceBinding(this, ItemContainerThemeProperty, resourceKey); + + this.AddTokenBindingDisposable( + TokenResourceBinder.CreateGlobalResourceBinding(this, ItemContainerThemeProperty, resourceKey)); } } @@ -331,6 +345,7 @@ public class NavMenu : NavMenuBase { item.IsSelected = false; } + foreach (var childItem in item.Items) { if (childItem is NavMenuItem navMenuItem) @@ -370,7 +385,7 @@ public class NavMenu : NavMenuBase } } } - + private void CloseInlineItemRecursively(NavMenuItem navMenuItem) { foreach (var item in navMenuItem.Items) @@ -380,10 +395,11 @@ public class NavMenu : NavMenuBase CloseInlineItemRecursively(childNavMenuItem); } } + navMenuItem.CloseInlineItem(); navMenuItem.IsSubMenuOpen = false; } - + internal void RaiseNavMenuItemClick(INavMenuItem navMenuItem) { RaiseEvent(new NavMenuItemClickEventArgs(NavMenuItemClickEvent, navMenuItem)); diff --git a/src/AtomUI.Controls/NumericUpDown/NumericUpDownTheme.cs b/src/AtomUI.Controls/NumericUpDown/NumericUpDownTheme.cs index 35a289e39..7f8956140 100644 --- a/src/AtomUI.Controls/NumericUpDown/NumericUpDownTheme.cs +++ b/src/AtomUI.Controls/NumericUpDown/NumericUpDownTheme.cs @@ -66,7 +66,8 @@ internal class NumericUpDownTheme : BaseControlTheme AcceptsReturn = false, EmbedMode = true }; - BindUtils.RelayBind(numericUpDown, DataValidationErrors.ErrorsProperty, textBox, DataValidationErrors.ErrorsProperty); + + CreateTemplateParentBinding(textBox, DataValidationErrors.ErrorsProperty, DataValidationErrors.ErrorsProperty); CreateTemplateParentBinding(textBox, TextBox.SizeTypeProperty, NumericUpDown.SizeTypeProperty); CreateTemplateParentBinding(textBox, Avalonia.Controls.TextBox.IsReadOnlyProperty, Avalonia.Controls.NumericUpDown.IsReadOnlyProperty); diff --git a/src/AtomUI.Theme/ITokenResourceConsumer.cs b/src/AtomUI.Theme/ITokenResourceConsumer.cs new file mode 100644 index 000000000..7413d18b9 --- /dev/null +++ b/src/AtomUI.Theme/ITokenResourceConsumer.cs @@ -0,0 +1,29 @@ +using System.Diagnostics; +using System.Reactive.Disposables; +using Avalonia; + +namespace AtomUI.Theme; + +public interface ITokenResourceConsumer +{ + CompositeDisposable? TokenBindingsDisposable { get; } +} + +public static class StyledElementTokenBindingsExtensions +{ + public static void AddTokenBindingDisposable(this StyledElement control, IDisposable tokenBindingsDisposable) + { + var consumer = control as ITokenResourceConsumer; + Debug.Assert(consumer != null, $"{control.GetType()} is not ITokenResourceConsumer"); + Debug.Assert(consumer.TokenBindingsDisposable != null, + $"The TokenBindingsDisposable of ITokenResourceConsumer {control.GetType()} is null."); + consumer.TokenBindingsDisposable.Add(tokenBindingsDisposable); + } + + public static void DisposeTokenBindings(this StyledElement control) + { + var consumer = control as ITokenResourceConsumer; + Debug.Assert(consumer != null, $"{control.GetType()} is not ITokenResourceConsumer"); + consumer.TokenBindingsDisposable?.Dispose(); + } +} \ No newline at end of file