diff --git a/src/AtomUI.Controls/Flyouts/Flyout.cs b/src/AtomUI.Controls/Flyouts/Flyout.cs index 7a07c9408..7b87aefeb 100644 --- a/src/AtomUI.Controls/Flyouts/Flyout.cs +++ b/src/AtomUI.Controls/Flyouts/Flyout.cs @@ -104,7 +104,9 @@ public class Flyout : PopupFlyoutBase #endregion private Classes? _classes; - + private CompositeDisposable? _presenterBindingDisposables; + private CompositeDisposable? _popupBindingDisposables; + /// /// Gets the Classes collection to apply to the FlyoutPresenter this Flyout is hosting /// @@ -150,12 +152,12 @@ public class Flyout : PopupFlyoutBase protected override Control CreatePresenter() { - var presenter = new FlyoutPresenter - { - [!FlyoutPresenter.ContentProperty] = this[!ContentProperty], - [!FlyoutPresenter.IsMotionEnabledProperty] = this[!IsMotionEnabledProperty], - [!FlyoutPresenter.IsShowArrowProperty] = this[!IsShowArrowEffectiveProperty], - }; + _presenterBindingDisposables?.Dispose(); + _presenterBindingDisposables = new CompositeDisposable(3); + var presenter = new FlyoutPresenter(); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, ContentProperty, presenter, FlyoutPresenter.ContentProperty)); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, presenter, FlyoutPresenter.IsMotionEnabledProperty)); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, IsShowArrowEffectiveProperty, presenter, FlyoutPresenter.IsShowArrowProperty)); CalculateShowArrowEffective(); SetupArrowPosition(Popup, presenter); return presenter; @@ -164,11 +166,13 @@ public class Flyout : PopupFlyoutBase protected internal override void NotifyPopupCreated(Popup popup) { base.NotifyPopupCreated(popup); - BindUtils.RelayBind(this, PlacementProperty, popup, Popup.PlacementProperty); - BindUtils.RelayBind(this, PlacementAnchorProperty, popup, Popup.PlacementAnchorProperty); - BindUtils.RelayBind(this, PlacementGravityProperty, popup, Popup.PlacementGravityProperty); - BindUtils.RelayBind(this, MaskShadowsProperty, popup, Popup.MaskShadowsProperty); - BindUtils.RelayBind(this, IsMotionEnabledProperty, popup, Popup.IsMotionEnabledProperty); + _popupBindingDisposables?.Dispose(); + _popupBindingDisposables = new CompositeDisposable(5); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, PlacementProperty, popup, Popup.PlacementProperty)); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, PlacementAnchorProperty, popup, Popup.PlacementAnchorProperty)); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, PlacementGravityProperty, popup, Popup.PlacementGravityProperty)); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, MaskShadowsProperty, popup, Popup.MaskShadowsProperty)); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, popup, Popup.IsMotionEnabledProperty)); SetupArrowPosition(popup); } diff --git a/src/AtomUI.Controls/Flyouts/MenuFlyout.cs b/src/AtomUI.Controls/Flyouts/MenuFlyout.cs index 8bccd0bfd..f21444965 100644 --- a/src/AtomUI.Controls/Flyouts/MenuFlyout.cs +++ b/src/AtomUI.Controls/Flyouts/MenuFlyout.cs @@ -1,5 +1,6 @@ using System.Collections; using System.ComponentModel; +using System.Reactive.Disposables; using AtomUI.Controls.Utils; using AtomUI.Data; using Avalonia; @@ -44,17 +45,17 @@ public class MenuFlyout : Flyout get => GetValue(ItemTemplateProperty); set => SetValue(ItemTemplateProperty, value); } - - #endregion - - public Func? ClickHideFlyoutPredicate; - - private IDisposable? _detectMouseClickDisposable; - private MenuFlyoutPresenter? _presenter; [Content] public ItemCollection Items { get; } + public Func? ClickHideFlyoutPredicate; + #endregion + + private IDisposable? _detectMouseClickDisposable; + private MenuFlyoutPresenter? _presenter; + private CompositeDisposable? _presenterBindingDisposables; + public MenuFlyout() { var itemCollectionType = typeof(ItemCollection); @@ -63,16 +64,16 @@ public class MenuFlyout : Flyout protected override Control CreatePresenter() { + _presenterBindingDisposables?.Dispose(); + _presenterBindingDisposables = new CompositeDisposable(4); _presenter = new MenuFlyoutPresenter { - ItemsSource = Items, - [!ItemsControl.ItemTemplateProperty] = this[!ItemTemplateProperty], - [!ItemsControl.ItemContainerThemeProperty] = this[!ItemContainerThemeProperty], - [!MenuFlyoutPresenter.IsMotionEnabledProperty] = this[!IsMotionEnabledProperty], - MenuFlyout = this + MenuFlyout = this }; - BindUtils.RelayBind(this, IsShowArrowEffectiveProperty, _presenter, MenuFlyoutPresenter.IsShowArrowProperty); - BindUtils.RelayBind(this, IsMotionEnabledProperty, _presenter, MenuFlyoutPresenter.IsMotionEnabledProperty); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, ItemTemplateProperty, _presenter, MenuFlyoutPresenter.ItemTemplateProperty)); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, ItemContainerThemeProperty, _presenter, MenuFlyoutPresenter.ItemContainerThemeProperty)); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, IsShowArrowEffectiveProperty, _presenter, MenuFlyoutPresenter.IsShowArrowProperty)); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, _presenter, MenuFlyoutPresenter.IsMotionEnabledProperty)); SetupArrowPosition(Popup, _presenter); CalculateShowArrowEffective(); @@ -173,6 +174,7 @@ public class MenuFlyout : Flyout { if (_detectMouseClickDisposable is not null) { + _detectMouseClickDisposable.Dispose(); CompositeDisposable?.Remove(_detectMouseClickDisposable); } @@ -184,6 +186,7 @@ public class MenuFlyout : Flyout { if (_detectMouseClickDisposable is not null) { + _detectMouseClickDisposable.Dispose(); CompositeDisposable?.Remove(_detectMouseClickDisposable); } } diff --git a/src/AtomUI.Controls/Flyouts/MenuFlyoutPresenter.cs b/src/AtomUI.Controls/Flyouts/MenuFlyoutPresenter.cs index df61f2955..c841ee684 100644 --- a/src/AtomUI.Controls/Flyouts/MenuFlyoutPresenter.cs +++ b/src/AtomUI.Controls/Flyouts/MenuFlyoutPresenter.cs @@ -1,4 +1,6 @@ -using System.Diagnostics; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Reactive.Disposables; using AtomUI.Controls.Themes; using AtomUI.Data; using AtomUI.Theme; @@ -76,17 +78,41 @@ public class MenuFlyoutPresenter : MenuBase, #endregion private ArrowDecoratedBox? _arrowDecoratedBox; + private Dictionary _itemsBindingDisposables = new(); public MenuFlyoutPresenter() : base(new DefaultMenuInteractionHandler(true)) { this.RegisterResources(); + Items.CollectionChanged += HandleCollectionChanged; } public MenuFlyoutPresenter(IMenuInteractionHandler menuInteractionHandler) : base(menuInteractionHandler) { this.RegisterResources(); + Items.CollectionChanged += HandleCollectionChanged; + } + + private void HandleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + { + if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems.Count > 0) + { + foreach (var item in e.OldItems) + { + if (item is MenuItem menuItem) + { + if (_itemsBindingDisposables.TryGetValue(menuItem, out var disposable)) + { + disposable.Dispose(); + _itemsBindingDisposables.Remove(menuItem); + } + } + } + } + } } public override void Close() @@ -244,7 +270,14 @@ public class MenuFlyoutPresenter : MenuBase, { if (container is MenuItem menuItem) { - BindUtils.RelayBind(this, IsMotionEnabledProperty, menuItem, MenuItem.IsMotionEnabledProperty); + var disposables = new CompositeDisposable(1); + disposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, menuItem, MenuItem.IsMotionEnabledProperty)); + if (_itemsBindingDisposables.TryGetValue(menuItem, out var oldDisposables)) + { + oldDisposables.Dispose(); + _itemsBindingDisposables.Remove(menuItem); + } + _itemsBindingDisposables.Add(menuItem, disposables); } base.PrepareContainerForItemOverride(container, item, index); diff --git a/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs b/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs index 0e2275ccb..fa0db5913 100644 --- a/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs +++ b/src/AtomUI.Controls/Flyouts/PopupFlyoutBase.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Reactive.Disposables; using AtomUI.Controls.Utils; using AtomUI.Data; using AtomUI.MotionScene; @@ -226,6 +227,7 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider private PixelRect? _enlargePopupRectScreenPixelRect; private IDisposable? _transientDisposable; private Action? _popupHostChangedHandler; + private CompositeDisposable? _popupBindingDisposables; static PopupFlyoutBase() { @@ -239,12 +241,14 @@ public abstract class PopupFlyoutBase : FlyoutBase, IPopupHostProvider protected internal virtual void NotifyPopupCreated(Popup popup) { - BindUtils.RelayBind(this, MarginToAnchorProperty, popup); - BindUtils.RelayBind(this, IsDetectMouseClickEnabledProperty, popup, Popup.IsDetectMouseClickEnabledProperty); - BindUtils.RelayBind(this, IsMotionEnabledProperty, popup, Popup.IsMotionEnabledProperty); - BindUtils.RelayBind(this, MotionDurationProperty, popup, Popup.MotionDurationProperty); - BindUtils.RelayBind(this, OpenMotionProperty, popup, Popup.OpenMotionProperty); - BindUtils.RelayBind(this, CloseMotionProperty, popup, Popup.CloseMotionProperty); + _popupBindingDisposables?.Dispose(); + _popupBindingDisposables = new CompositeDisposable(6); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, MarginToAnchorProperty, popup)); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, IsDetectMouseClickEnabledProperty, popup, Popup.IsDetectMouseClickEnabledProperty)); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, popup, Popup.IsMotionEnabledProperty)); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, MotionDurationProperty, popup, Popup.MotionDurationProperty)); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, OpenMotionProperty, popup, Popup.OpenMotionProperty)); + _popupBindingDisposables.Add(BindUtils.RelayBind(this, CloseMotionProperty, popup, Popup.CloseMotionProperty)); PopupCreated?.Invoke(this, new FlyoutPopupCreatedEventArgs(popup)); } diff --git a/src/AtomUI.Controls/Flyouts/TreeViewFlyout.cs b/src/AtomUI.Controls/Flyouts/TreeViewFlyout.cs index 01f91af99..d99afe91c 100644 --- a/src/AtomUI.Controls/Flyouts/TreeViewFlyout.cs +++ b/src/AtomUI.Controls/Flyouts/TreeViewFlyout.cs @@ -1,5 +1,6 @@ using System.Collections; using System.ComponentModel; +using System.Reactive.Disposables; using AtomUI.Controls.Utils; using AtomUI.Data; using Avalonia; @@ -44,15 +45,15 @@ public class TreeViewFlyout : Flyout get => GetValue(ItemTemplateProperty); set => SetValue(ItemTemplateProperty, value); } - - #endregion - + + [Content] public ItemCollection Items { get; } public Func? ClickHideFlyoutPredicate; + #endregion + private IDisposable? _detectMouseClickDisposable; - - [Content] public ItemCollection Items { get; } - + private CompositeDisposable? _presenterBindingDisposables; + public TreeViewFlyout() { var itemCollectionType = typeof(ItemCollection); @@ -61,16 +62,17 @@ public class TreeViewFlyout : Flyout protected override Control CreatePresenter() { + _presenterBindingDisposables?.Dispose(); + _presenterBindingDisposables = new CompositeDisposable(4); var presenter = new TreeViewFlyoutPresenter { - ItemsSource = Items, - [!ItemsControl.ItemTemplateProperty] = this[!ItemTemplateProperty], - [!ItemsControl.ItemContainerThemeProperty] = this[!ItemContainerThemeProperty], - + ItemsSource = Items }; presenter.TreeViewFlyout = this; - BindUtils.RelayBind(this, IsShowArrowEffectiveProperty, presenter, TreeViewFlyoutPresenter.IsShowArrowProperty); - BindUtils.RelayBind(this, IsMotionEnabledProperty, presenter, TreeViewFlyoutPresenter.IsMotionEnabledProperty); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, ItemTemplateProperty, presenter, TreeViewFlyoutPresenter.ItemTemplateProperty)); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, ItemContainerThemeProperty, presenter, TreeViewFlyoutPresenter.ItemContainerThemeProperty)); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, IsShowArrowEffectiveProperty, presenter, TreeViewFlyoutPresenter.IsShowArrowProperty)); + _presenterBindingDisposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, presenter, TreeViewFlyoutPresenter.IsMotionEnabledProperty)); SetupArrowPosition(Popup, presenter); CalculateShowArrowEffective(); @@ -172,6 +174,7 @@ public class TreeViewFlyout : Flyout { if (_detectMouseClickDisposable is not null) { + _detectMouseClickDisposable.Dispose(); CompositeDisposable?.Remove(_detectMouseClickDisposable); } @@ -183,6 +186,7 @@ public class TreeViewFlyout : Flyout { if (_detectMouseClickDisposable is not null) { + _detectMouseClickDisposable.Dispose(); CompositeDisposable?.Remove(_detectMouseClickDisposable); } } diff --git a/src/AtomUI.Controls/Flyouts/TreeViewFlyoutPresenter.cs b/src/AtomUI.Controls/Flyouts/TreeViewFlyoutPresenter.cs index 21b6cb545..85a99e92f 100644 --- a/src/AtomUI.Controls/Flyouts/TreeViewFlyoutPresenter.cs +++ b/src/AtomUI.Controls/Flyouts/TreeViewFlyoutPresenter.cs @@ -1,3 +1,5 @@ +using System.Collections.Specialized; +using System.Reactive.Disposables; using AtomUI.Controls.Themes; using AtomUI.Data; using AtomUI.Theme; @@ -68,16 +70,41 @@ public class TreeViewFlyoutPresenter : FloatableTreeView, #endregion + private Dictionary _itemsBindingDisposables = new(); + public TreeViewFlyoutPresenter() : base(new DefaultTreeViewInteractionHandler(true)) { this.RegisterResources(); + Items.CollectionChanged += HandleCollectionChanged; } public TreeViewFlyoutPresenter(ITreeViewInteractionHandler menuInteractionHandler) : base(menuInteractionHandler) { this.RegisterResources(); + Items.CollectionChanged += HandleCollectionChanged; + } + + private void HandleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + { + if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems.Count > 0) + { + foreach (var item in e.OldItems) + { + if (item is MenuItem menuItem) + { + if (_itemsBindingDisposables.TryGetValue(menuItem, out var disposable)) + { + disposable.Dispose(); + _itemsBindingDisposables.Remove(menuItem); + } + } + } + } + } } protected override void ContainerForItemPreparedOverride(Control container, object? item, int index) @@ -172,7 +199,14 @@ public class TreeViewFlyoutPresenter : FloatableTreeView, { if (container is MenuItem menuItem) { - BindUtils.RelayBind(this, IsMotionEnabledProperty, menuItem, MenuItem.IsMotionEnabledProperty); + var disposables = new CompositeDisposable(1); + disposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, menuItem, MenuItem.IsMotionEnabledProperty)); + if (_itemsBindingDisposables.TryGetValue(menuItem, out var oldDisposables)) + { + oldDisposables.Dispose(); + _itemsBindingDisposables.Remove(menuItem); + } + _itemsBindingDisposables.Add(menuItem, disposables); } base.PrepareContainerForItemOverride(container, item, index);