mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-17 05:31:01 +08:00
Bugfix(MemoryLeak): fix IconPresenter memory leak
This commit is contained in:
@@ -141,6 +141,7 @@ public partial class FormShowCase : ReactiveUserControl<FormViewModel>
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_messageManager?.Dispose();
|
||||
_messageManager = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -192,14 +192,18 @@ public partial class UploadShowCase : ReactiveUserControl<UploadViewModel>
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
if (_messageManager == null)
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
_messageManager = new WindowMessageManager(topLevel)
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
_messageManager = new WindowMessageManager(topLevel)
|
||||
{
|
||||
MaxItems = 10
|
||||
};
|
||||
}
|
||||
MaxItems = 10
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_messageManager?.Dispose();
|
||||
_messageManager = null;
|
||||
}
|
||||
|
||||
private void HandleAboutToScheduling(object? sender, UploadTaskAboutToSchedulingEventArgs e)
|
||||
|
||||
@@ -28,6 +28,13 @@ public partial class MessageShowCase : ReactiveUserControl<MessageViewModel>
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_messageManager?.Dispose();
|
||||
_messageManager = null;
|
||||
}
|
||||
|
||||
private void ShowSimpleMessage(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
_messageManager?.Show(new Message(
|
||||
|
||||
@@ -98,6 +98,18 @@ public partial class NotificationShowCase : ReactiveUserControl<NotificationView
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_basicManager?.Dispose();
|
||||
_topLeftManager?.Dispose();
|
||||
_topManager?.Dispose();
|
||||
_topRightManager?.Dispose();
|
||||
_bottomLeftManager?.Dispose();
|
||||
_bottomManager?.Dispose();
|
||||
_bottomRightManager?.Dispose();
|
||||
}
|
||||
|
||||
private void ShowSimpleNotification(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
_basicManager?.Show(new Notification(
|
||||
|
||||
@@ -62,6 +62,13 @@ public partial class BreadcrumbShowCase : ReactiveUserControl<BreadcrumbViewMode
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_messageManager?.Dispose();
|
||||
_messageManager = null;
|
||||
}
|
||||
|
||||
private void HandleNavigateRequest(object? sender, BreadcrumbNavigateEventArgs eventArgs)
|
||||
{
|
||||
_messageManager?.Show(new Message(
|
||||
|
||||
@@ -13,9 +13,6 @@ using IconControl = Icon;
|
||||
public class IconPresenter : Control, IMotionAwareControl
|
||||
{
|
||||
#region 公共属性定义
|
||||
public static readonly StyledProperty<IconTemplate?> IconTemplateProperty =
|
||||
AvaloniaProperty.Register<IconPresenter, IconTemplate?>(nameof(IconTemplate));
|
||||
|
||||
public static readonly StyledProperty<PathIcon?> IconProperty =
|
||||
AvaloniaProperty.Register<IconPresenter, PathIcon?>(nameof(Icon));
|
||||
|
||||
@@ -25,12 +22,6 @@ public class IconPresenter : Control, IMotionAwareControl
|
||||
public static readonly StyledProperty<bool> IsMotionEnabledProperty =
|
||||
MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<IconPresenter>();
|
||||
|
||||
public IconTemplate? IconTemplate
|
||||
{
|
||||
get => GetValue(IconTemplateProperty);
|
||||
set => SetValue(IconTemplateProperty, value);
|
||||
}
|
||||
|
||||
[Content]
|
||||
public PathIcon? Icon
|
||||
{
|
||||
@@ -56,10 +47,9 @@ public class IconPresenter : Control, IMotionAwareControl
|
||||
|
||||
static IconPresenter()
|
||||
{
|
||||
AffectsMeasure<IconPresenter>(IconProperty, IconTemplateProperty);
|
||||
AffectsMeasure<IconPresenter>(IconProperty);
|
||||
AffectsRender<IconPresenter>(IconBrushProperty);
|
||||
IconProperty.Changed.AddClassHandler<IconPresenter>((x, e) => x.HandleIconChanged(e));
|
||||
IconTemplateProperty.Changed.AddClassHandler<IconPresenter>((x, e) => x.HandleIconTemplateChanged(e));
|
||||
}
|
||||
|
||||
private void HandleIconChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
@@ -77,34 +67,33 @@ public class IconPresenter : Control, IMotionAwareControl
|
||||
|
||||
if (newChild is PathIcon pathIcon)
|
||||
{
|
||||
ConfigureIcon(pathIcon);
|
||||
AddIcon(pathIcon);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleIconTemplateChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
var oldIconTemplate = (IconTemplate?)change.OldValue;
|
||||
var newIconTemplate = (IconTemplate?)change.NewValue;
|
||||
if (oldIconTemplate != null)
|
||||
base.OnAttachedToVisualTree(e);
|
||||
if (Icon != null)
|
||||
{
|
||||
_disposables?.Dispose();
|
||||
_disposables = null;
|
||||
if (Icon != null)
|
||||
{
|
||||
((ISetLogicalParent)Icon).SetParent(null);
|
||||
}
|
||||
LogicalChildren.Clear();
|
||||
VisualChildren.Clear();
|
||||
ConfigureIcon(Icon);
|
||||
}
|
||||
}
|
||||
|
||||
if (newIconTemplate != null)
|
||||
{
|
||||
var pathIcon = newIconTemplate.Build();
|
||||
if (pathIcon != null)
|
||||
{
|
||||
ConfigureIcon(pathIcon);
|
||||
}
|
||||
}
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_disposables?.Dispose();
|
||||
_disposables = null;
|
||||
}
|
||||
|
||||
private void AddIcon(PathIcon pathIcon)
|
||||
{
|
||||
ConfigureIcon(pathIcon);
|
||||
((ISetLogicalParent)pathIcon).SetParent(null);
|
||||
pathIcon.SetVisualParent(null);
|
||||
VisualChildren.Add(pathIcon);
|
||||
LogicalChildren.Add(pathIcon);
|
||||
}
|
||||
|
||||
private void ConfigureIcon(PathIcon pathIcon)
|
||||
@@ -123,9 +112,5 @@ public class IconPresenter : Control, IMotionAwareControl
|
||||
{
|
||||
_disposables.Add(BindUtils.RelayBind(this, IconBrushProperty, pathIcon, PathIcon.ForegroundProperty, BindingMode.Default, BindingPriority.Template));
|
||||
}
|
||||
((ISetLogicalParent)pathIcon).SetParent(null);
|
||||
pathIcon.SetVisualParent(null);
|
||||
VisualChildren.Add(pathIcon);
|
||||
LogicalChildren.Add(pathIcon);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Metadata;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class IconTemplatePresenter : Control, IMotionAwareControl
|
||||
{
|
||||
#region 公共属性定义
|
||||
public static readonly StyledProperty<IconTemplate?> IconTemplateProperty =
|
||||
AvaloniaProperty.Register<IconTemplatePresenter, IconTemplate?>(nameof(IconTemplate));
|
||||
|
||||
public static readonly StyledProperty<IBrush?> IconBrushProperty =
|
||||
AvaloniaProperty.Register<IconTemplatePresenter, IBrush?>(nameof(IconBrush));
|
||||
|
||||
public static readonly StyledProperty<bool> IsMotionEnabledProperty =
|
||||
MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<IconTemplatePresenter>();
|
||||
|
||||
[Content]
|
||||
public IconTemplate? IconTemplate
|
||||
{
|
||||
get => GetValue(IconTemplateProperty);
|
||||
set => SetValue(IconTemplateProperty, value);
|
||||
}
|
||||
|
||||
public IBrush? IconBrush
|
||||
{
|
||||
get => GetValue(IconBrushProperty);
|
||||
set => SetValue(IconBrushProperty, value);
|
||||
}
|
||||
|
||||
public bool IsMotionEnabled
|
||||
{
|
||||
get => GetValue(IsMotionEnabledProperty);
|
||||
set => SetValue(IsMotionEnabledProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
static IconTemplatePresenter()
|
||||
{
|
||||
AffectsMeasure<IconTemplatePresenter>( IconTemplateProperty);
|
||||
AffectsRender<IconTemplatePresenter>(IconBrushProperty);
|
||||
IconTemplateProperty.Changed.AddClassHandler<IconTemplatePresenter>((x, e) => x.HandleIconTemplateChanged(e));
|
||||
}
|
||||
|
||||
private void HandleIconTemplateChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
var oldIconTemplate = (IconTemplate?)change.OldValue;
|
||||
var newIconTemplate = (IconTemplate?)change.NewValue;
|
||||
if (oldIconTemplate != null)
|
||||
{
|
||||
LogicalChildren.Clear();
|
||||
VisualChildren.Clear();
|
||||
}
|
||||
|
||||
if (newIconTemplate != null)
|
||||
{
|
||||
var pathIcon = newIconTemplate.Build();
|
||||
if (pathIcon != null)
|
||||
{
|
||||
ConfigureIcon(pathIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureIcon(PathIcon pathIcon)
|
||||
{
|
||||
pathIcon[!WidthProperty] = this[!WidthProperty];
|
||||
pathIcon[!HeightProperty] = this[!HeightProperty];
|
||||
pathIcon[!IsMotionEnabledProperty] = this[!IsMotionEnabledProperty];
|
||||
pathIcon[!Icon.StrokeBrushProperty] = this[!IconBrushProperty];
|
||||
pathIcon[!Icon.FillBrushProperty] = this[!IconBrushProperty];
|
||||
VisualChildren.Add(pathIcon);
|
||||
LogicalChildren.Add(pathIcon);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@
|
||||
VerticalContentAlignment="Center"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}" />
|
||||
<atom:IconPresenter Name="ExpandIconPresenter" Grid.Column="3"
|
||||
<atom:IconTemplatePresenter Name="ExpandIconPresenter" Grid.Column="3"
|
||||
IconTemplate="{TemplateBinding ExpandIcon}"
|
||||
IsMotionEnabled="{TemplateBinding IsMotionEnabled}">
|
||||
<atom:IconPresenter.IsVisible>
|
||||
@@ -58,10 +58,10 @@
|
||||
Path="IsLoading" />
|
||||
</MultiBinding>
|
||||
</atom:IconPresenter.IsVisible>
|
||||
</atom:IconPresenter>
|
||||
<atom:IconPresenter Name="LoadingIconPresenter" Grid.Column="3"
|
||||
IsVisible="{TemplateBinding IsLoading}"
|
||||
IconTemplate="{TemplateBinding LoadingIcon}" />
|
||||
</atom:IconTemplatePresenter>
|
||||
<atom:IconTemplatePresenter Name="LoadingIconPresenter" Grid.Column="3"
|
||||
IsVisible="{TemplateBinding IsLoading}"
|
||||
IconTemplate="{TemplateBinding LoadingIcon}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<atom:IconPresenter
|
||||
<atom:IconTemplatePresenter
|
||||
Name="SelectedIndicator"
|
||||
IconTemplate="{TemplateBinding SelectedIndicator}"
|
||||
IsVisible="{TemplateBinding IsSelectedIndicatorVisible}"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<atom:IconPresenter
|
||||
<atom:IconTemplatePresenter
|
||||
Name="SelectedIndicator"
|
||||
IconTemplate="{TemplateBinding SelectedIndicator}"
|
||||
IsVisible="{TemplateBinding IsSelectedIndicatorVisible}"
|
||||
|
||||
@@ -5,7 +5,6 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
@@ -101,8 +100,11 @@ public class WindowMessageManager : TemplatedControl, IMessageManager, IMotionAw
|
||||
/// <param name="classes">style classes to apply</param>
|
||||
public void Show(IMessage message, string[]? classes = null)
|
||||
{
|
||||
var expiration = message.Expiration;
|
||||
var onClose = message.OnClose;
|
||||
if (_isDisposed || _items is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.VerifyAccess();
|
||||
|
||||
var messageControl = new MessageCard
|
||||
@@ -114,7 +116,7 @@ public class WindowMessageManager : TemplatedControl, IMessageManager, IMotionAw
|
||||
messageControl[!MessageCard.IsMotionEnabledProperty] = this[!IsMotionEnabledProperty];
|
||||
|
||||
// Add style classes if any
|
||||
if (classes != null)
|
||||
if (classes?.Length > 0)
|
||||
{
|
||||
foreach (var cls in classes)
|
||||
{
|
||||
@@ -122,28 +124,40 @@ public class WindowMessageManager : TemplatedControl, IMessageManager, IMotionAw
|
||||
}
|
||||
}
|
||||
|
||||
messageControl.MessageClosed += (sender, args) =>
|
||||
messageControl.MessageClosed += (sender, _) =>
|
||||
{
|
||||
onClose?.Invoke();
|
||||
_items?.Remove(sender);
|
||||
message.OnClose?.Invoke();
|
||||
_items.Remove(sender);
|
||||
};
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_items?.Add(messageControl);
|
||||
|
||||
if (_items?.OfType<MessageCard>().Count(i => !i.IsClosing) > MaxItems)
|
||||
{
|
||||
_items.OfType<MessageCard>().First(i => !i.IsClosing).Close();
|
||||
}
|
||||
_items.Add(messageControl);
|
||||
RemoveExcessMessages();
|
||||
});
|
||||
|
||||
if (expiration == TimeSpan.Zero)
|
||||
// Auto-close after expiration time
|
||||
if (message.Expiration != TimeSpan.Zero)
|
||||
{
|
||||
return;
|
||||
DispatcherTimer.RunOnce(messageControl.Close, message.Expiration);
|
||||
}
|
||||
}
|
||||
|
||||
DispatcherTimer.RunOnce(messageControl.Close, expiration);
|
||||
/// <summary>
|
||||
/// Removes excess messages when the count exceeds MaxItems.
|
||||
/// </summary>
|
||||
private void RemoveExcessMessages()
|
||||
{
|
||||
var visibleMessages = _items!.OfType<MessageCard>().Where(m => !m.IsClosing).ToList();
|
||||
var excessCount = visibleMessages.Count - MaxItems;
|
||||
|
||||
if (excessCount > 0)
|
||||
{
|
||||
for (int i = 0; i < excessCount; i++)
|
||||
{
|
||||
visibleMessages[i].Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InstallFromTopLevel(TopLevel topLevel)
|
||||
@@ -157,7 +171,7 @@ public class WindowMessageManager : TemplatedControl, IMessageManager, IMotionAw
|
||||
}
|
||||
}
|
||||
|
||||
private void UninstallFromTopLevel()
|
||||
public void Dispose()
|
||||
{
|
||||
if (_topLevel is null || _isDisposed)
|
||||
{
|
||||
@@ -168,7 +182,7 @@ public class WindowMessageManager : TemplatedControl, IMessageManager, IMotionAw
|
||||
{
|
||||
// 卸载事件订阅
|
||||
_topLevel.TemplateApplied -= TopLevelOnTemplateApplied;
|
||||
|
||||
_items?.Clear();
|
||||
// 从 AdornerLayer 中移除
|
||||
if (_adornerLayer is not null)
|
||||
{
|
||||
@@ -176,7 +190,6 @@ public class WindowMessageManager : TemplatedControl, IMessageManager, IMotionAw
|
||||
AdornerLayer.SetAdornedElement(this, null);
|
||||
_adornerLayer = null;
|
||||
}
|
||||
|
||||
_topLevel = null;
|
||||
_items = null;
|
||||
_isDisposed = true;
|
||||
@@ -187,41 +200,23 @@ public class WindowMessageManager : TemplatedControl, IMessageManager, IMotionAw
|
||||
}
|
||||
}
|
||||
|
||||
private void TopLevelOnTemplateApplied(object? sender, TemplateAppliedEventArgs e)
|
||||
private void RemoveFromAdornerLayer()
|
||||
{
|
||||
if (_adornerLayer is not null)
|
||||
{
|
||||
_adornerLayer.Children.Remove(this);
|
||||
AdornerLayer.SetAdornedElement(this, null);
|
||||
_adornerLayer = null;
|
||||
}
|
||||
_adornerLayer = null;
|
||||
}
|
||||
|
||||
private void TopLevelOnTemplateApplied(object? sender, TemplateAppliedEventArgs _)
|
||||
{
|
||||
RemoveFromAdornerLayer();
|
||||
|
||||
// Reinstall notification manager on template reapplied.
|
||||
var topLevel = (TopLevel)sender!;
|
||||
topLevel.TemplateApplied -= TopLevelOnTemplateApplied;
|
||||
InstallFromTopLevel(topLevel);
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
UninstallFromTopLevel();
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
|
||||
if (_topLevel is not null && !_isDisposed)
|
||||
{
|
||||
_topLevel.TemplateApplied += TopLevelOnTemplateApplied;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUnloaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnUnloaded(e);
|
||||
UninstallFromTopLevel();
|
||||
}
|
||||
|
||||
public void Dispose() => UninstallFromTopLevel();
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections;
|
||||
using AtomUI.Controls;
|
||||
using AtomUI.Data;
|
||||
using AtomUI.Theme;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
@@ -18,14 +17,8 @@ namespace AtomUI.Desktop.Controls;
|
||||
NotificationPseudoClass.BottomRight,
|
||||
NotificationPseudoClass.TopCenter,
|
||||
NotificationPseudoClass.BottomCenter)]
|
||||
public class WindowNotificationManager : TemplatedControl, INotificationManager, IMotionAwareControl
|
||||
public class WindowNotificationManager : TemplatedControl, INotificationManager, IMotionAwareControl, IDisposable
|
||||
{
|
||||
private IList? _items;
|
||||
private readonly Queue<NotificationCard> _cleanupQueue;
|
||||
private readonly DispatcherTimer _cardExpiredTimer;
|
||||
private readonly DispatcherTimer _cleanupTimer;
|
||||
private IDisposable? _bindingDisposable;
|
||||
|
||||
#region 公共属性定义
|
||||
public static readonly StyledProperty<NotificationPosition> PositionProperty =
|
||||
AvaloniaProperty.Register<WindowNotificationManager, NotificationPosition>(
|
||||
@@ -67,6 +60,12 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager,
|
||||
#endregion
|
||||
|
||||
private TopLevel? _topLevel;
|
||||
private bool _isDisposed;
|
||||
private AdornerLayer? _adornerLayer;
|
||||
private IList? _items;
|
||||
private readonly Queue<NotificationCard> _cleanupQueue;
|
||||
private readonly DispatcherTimer _cardExpiredTimer;
|
||||
private readonly DispatcherTimer _cleanupTimer;
|
||||
|
||||
public WindowNotificationManager(TopLevel? host) : this()
|
||||
{
|
||||
@@ -153,6 +152,10 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager,
|
||||
|
||||
public void Show(INotification notification, string[]? classes = null)
|
||||
{
|
||||
if (_items is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var expiration = notification.Expiration;
|
||||
var onClick = notification.OnClick;
|
||||
var onClose = notification.OnClose;
|
||||
@@ -167,11 +170,10 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager,
|
||||
Expiration = expiration == TimeSpan.Zero ? null : expiration,
|
||||
IsShowProgress = notification.ShowProgress
|
||||
};
|
||||
_bindingDisposable?.Dispose();
|
||||
_bindingDisposable = BindUtils.RelayBind(this, PositionProperty, notificationControl, NotificationCard.PositionProperty);
|
||||
|
||||
notificationControl[!NotificationCard.PositionProperty] = this[!PositionProperty];
|
||||
|
||||
// Add style classes if any
|
||||
if (classes != null)
|
||||
if (classes?.Length > 0)
|
||||
{
|
||||
foreach (var cls in classes)
|
||||
{
|
||||
@@ -179,11 +181,10 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager,
|
||||
}
|
||||
}
|
||||
|
||||
notificationControl.PointerPressed += (sender, args) => { onClick?.Invoke(); };
|
||||
notificationControl.NotificationClosed += (sender, args) =>
|
||||
notificationControl.PointerPressed += (_, _) => { onClick?.Invoke(); };
|
||||
notificationControl.NotificationClosed += (sender, _) =>
|
||||
{
|
||||
onClose?.Invoke();
|
||||
_bindingDisposable?.Dispose();
|
||||
if (sender is NotificationCard card)
|
||||
{
|
||||
_items?.Remove(card);
|
||||
@@ -195,12 +196,27 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager,
|
||||
{
|
||||
_items?.Add(notificationControl);
|
||||
ConfigureExpiredTimer();
|
||||
if (_items?.OfType<NotificationCard>().Count(i => !i.IsClosing) > MaxItems)
|
||||
{
|
||||
_items.OfType<NotificationCard>().First(i => !i.IsClosing).Close();
|
||||
}
|
||||
RemoveExcessNotifications();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes excess notifications when the count exceeds MaxItems.
|
||||
/// </summary>
|
||||
private void RemoveExcessNotifications()
|
||||
{
|
||||
var visibleNotifications = _items!.OfType<NotificationCard>().Where(n => !n.IsClosing).ToList();
|
||||
var excessCount = visibleNotifications.Count - MaxItems;
|
||||
|
||||
if (excessCount > 0)
|
||||
{
|
||||
// 支持关闭多个超限的通知,而不仅仅是一个
|
||||
for (int i = 0; i < excessCount; i++)
|
||||
{
|
||||
visibleNotifications[i].Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
@@ -211,53 +227,72 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager,
|
||||
UpdatePseudoClasses(change.GetNewValue<NotificationPosition>());
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
ConfigureExpiredTimer();
|
||||
if (_topLevel is not null)
|
||||
{
|
||||
_topLevel.TemplateApplied += TopLevelOnTemplateApplied;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_items?.Clear();
|
||||
_cardExpiredTimer.Stop();
|
||||
_cleanupTimer.Stop();
|
||||
if (_topLevel is not null)
|
||||
{
|
||||
_topLevel.TemplateApplied -= TopLevelOnTemplateApplied;
|
||||
}
|
||||
}
|
||||
|
||||
private void InstallFromTopLevel(TopLevel topLevel)
|
||||
{
|
||||
topLevel.TemplateApplied += TopLevelOnTemplateApplied;
|
||||
var adorner = topLevel.FindDescendantOfType<VisualLayerManager>()?.AdornerLayer;
|
||||
if (adorner is not null)
|
||||
_adornerLayer = topLevel.FindDescendantOfType<VisualLayerManager>()?.AdornerLayer;
|
||||
if (_adornerLayer is not null)
|
||||
{
|
||||
adorner.Children.Add(this);
|
||||
AdornerLayer.SetAdornedElement(this, adorner);
|
||||
_adornerLayer.Children.Add(this);
|
||||
AdornerLayer.SetAdornedElement(this, _adornerLayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void TopLevelOnTemplateApplied(object? sender, TemplateAppliedEventArgs e)
|
||||
{
|
||||
if (Parent is AdornerLayer adornerLayer)
|
||||
{
|
||||
adornerLayer.Children.Remove(this);
|
||||
AdornerLayer.SetAdornedElement(this, null);
|
||||
}
|
||||
RemoveFromAdornerLayer();
|
||||
|
||||
// Reinstall notification manager on template reapplied.
|
||||
var topLevel = (TopLevel)sender!;
|
||||
topLevel.TemplateApplied -= TopLevelOnTemplateApplied;
|
||||
InstallFromTopLevel(topLevel);
|
||||
}
|
||||
|
||||
private void RemoveFromAdornerLayer()
|
||||
{
|
||||
if (_adornerLayer is not null)
|
||||
{
|
||||
_adornerLayer.Children.Remove(this);
|
||||
AdornerLayer.SetAdornedElement(this, null);
|
||||
_adornerLayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_topLevel is null || _isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_cleanupTimer.Stop();
|
||||
_cardExpiredTimer.Stop();
|
||||
_items?.Clear();
|
||||
_cleanupQueue.Clear();
|
||||
// 卸载事件订阅
|
||||
_topLevel.TemplateApplied -= TopLevelOnTemplateApplied;
|
||||
|
||||
// 从 AdornerLayer 中移除
|
||||
if (_adornerLayer is not null)
|
||||
{
|
||||
_adornerLayer.Children.Remove(this);
|
||||
AdornerLayer.SetAdornedElement(this, null);
|
||||
_adornerLayer = null;
|
||||
}
|
||||
|
||||
_topLevel = null;
|
||||
_items = null;
|
||||
_isDisposed = true;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error uninstalling from TopLevel: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePseudoClasses(NotificationPosition position)
|
||||
{
|
||||
@@ -278,4 +313,5 @@ public class WindowNotificationManager : TemplatedControl, INotificationManager,
|
||||
{
|
||||
_cardExpiredTimer.Start();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user