using System.ComponentModel; namespace AntDesignWPF.Controls; public enum NotificationPlacement { TopRight, TopLeft, BottomRight, BottomLeft } //public static class NotificationManager //{ // private static readonly Dictionary> _activeNotifications = new Dictionary> // { // { NotificationPlacement.TopRight, new List() }, // { NotificationPlacement.TopLeft, new List() }, // { NotificationPlacement.BottomRight, new List() }, // { NotificationPlacement.BottomLeft, new List() } // }; // private static readonly object _lock = new object(); // public static void Show(string title, string message, NotificationType type = NotificationType.Info, NotificationPlacement placement = NotificationPlacement.TopRight, int durationSeconds = 3) // { // var duration = TimeSpan.FromSeconds(durationSeconds); // Application.Current.Dispatcher.Invoke(() => // { // var notificationWindow = new NotificationView(title, message, type, duration); // notificationWindow.Closed += (s, e) => // { // lock (_lock) // { // _activeNotifications[placement].Remove(notificationWindow); // Reposition(placement); // } // }; // lock (_lock) // { // _activeNotifications[placement].Add(notificationWindow); // Reposition(placement); // } // notificationWindow.Show(); // }); // } // private static void Reposition(NotificationPlacement placement) // { // var workArea = SystemParameters.WorkArea; // var notifications = _activeNotifications[placement]; // double currentTop = workArea.Top + 10; // double currentBottom = workArea.Bottom - 10; // foreach (var window in notifications) // { // // 如果窗口尚未加载,先在屏幕外显示以获取其实际尺寸 // if (!window.IsLoaded) // { // window.WindowStartupLocation = WindowStartupLocation.Manual; // window.Left = -9999; // window.Top = -9999; // // 调用Show()会使窗口加载并经过布局计算 // // 对于非模态窗口,这不会阻塞代码执行 // window.Show(); // } // // 经过Show()之后,ActualHeight和ActualWidth就已经是准确的了 // if (placement == NotificationPlacement.TopRight || placement == NotificationPlacement.TopLeft) // { // window.Top = currentTop; // currentTop += window.ActualHeight + 10; // } // else // BottomRight or BottomLeft // { // window.Top = currentBottom - window.ActualHeight; // currentBottom -= (window.ActualHeight + 10); // } // if (placement == NotificationPlacement.TopRight || placement == NotificationPlacement.BottomRight) // { // // 在这里应该使用ActualWidth,特别是当窗口宽度为"Auto"时 // window.Left = workArea.Right - window.ActualWidth - 10; // } // else // TopLeft or BottomLeft // { // window.Left = workArea.Left + 10; // } // } // } //} public static class NotificationManager { // 使用 ConditionalWeakTable 来将通知列表与父窗口关联。 // 这有助于自动处理内存,当父窗口被垃圾回收时,关联的通知列表也会被回收,避免了内存泄漏。 private static readonly System.Runtime.CompilerServices.ConditionalWeakTable>> _windowNotifications = new System.Runtime.CompilerServices.ConditionalWeakTable>>(); // 保留一个用于全局通知的静态字典(当 owner 为 null 时使用) private static readonly Dictionary> _globalNotifications = new Dictionary> { { NotificationPlacement.TopRight, new List() }, { NotificationPlacement.TopLeft, new List() }, { NotificationPlacement.BottomRight, new List() }, { NotificationPlacement.BottomLeft, new List() } }; private static readonly object _lock = new object(); /// /// 显示一个通知。 /// /// 通知要附着到的父窗口。如果为 null,则显示为全局通知。 /// 标题 /// 消息 /// 类型 /// 位置 /// 显示时长(秒) public static void Show(Window owner, string title, string message, NotificationType type = NotificationType.Info, NotificationPlacement placement = NotificationPlacement.TopRight, int durationSeconds = 3) { var duration = TimeSpan.FromSeconds(durationSeconds); // 必须在UI线程上执行 Application.Current.Dispatcher.Invoke(() => { var notificationWindow = new NotificationView(title, message, type, duration); notificationWindow.Owner = owner; // 设置所有者 // 获取或创建与此窗口关联的通知列表 var activeNotifications = GetNotificationListForOwner(owner); notificationWindow.Closed += (s, e) => { lock (_lock) { activeNotifications[placement].Remove(notificationWindow); // 如果这是父窗口的最后一个通知,则取消事件订阅 if (owner != null && !HasActiveNotifications(owner)) { UnsubscribeFromOwnerEvents(owner); } Reposition(owner, placement); } }; lock (_lock) { // 如果是第一个为该窗口创建的通知,则订阅事件 if (owner != null && !HasActiveNotifications(owner)) { SubscribeToOwnerEvents(owner); } activeNotifications[placement].Add(notificationWindow); Reposition(owner, placement); } // Show() 必须在 Reposition 之后调用,以确保初始位置正确 notificationWindow.Show(); }); } // 重载一个 Show 方法以保持与旧 API 的兼容性(全局通知) public static void Show(string title, string message, NotificationType type = NotificationType.Info, NotificationPlacement placement = NotificationPlacement.TopRight, int durationSeconds = 3) { Show(null, title, message, type, placement, durationSeconds); } private static Dictionary> GetNotificationListForOwner(Window owner) { if (owner == null) { return _globalNotifications; } lock (_lock) { return _windowNotifications.GetValue(owner, key => { // 当 ConditionalWeakTable 中没有找到该窗口的条目时,此工厂方法被调用 return new Dictionary> { { NotificationPlacement.TopRight, new List() }, { NotificationPlacement.TopLeft, new List() }, { NotificationPlacement.BottomRight, new List() }, { NotificationPlacement.BottomLeft, new List() } }; }); } } private static bool HasActiveNotifications(Window owner) { if (owner == null) return false; var lists = GetNotificationListForOwner(owner); foreach (var list in lists.Values) { if (list.Count > 0) return true; } return false; } private static void SubscribeToOwnerEvents(Window owner) { owner.LocationChanged += OnOwnerMoved; owner.SizeChanged += OnOwnerMoved; owner.StateChanged += OnOwnerStateChanged; owner.Closing += OnOwnerClosing; // 确保在父窗口关闭时清理 } private static void UnsubscribeFromOwnerEvents(Window owner) { owner.LocationChanged -= OnOwnerMoved; owner.SizeChanged -= OnOwnerMoved; owner.StateChanged -= OnOwnerStateChanged; owner.Closing -= OnOwnerClosing; } private static void OnOwnerClosing(object sender, CancelEventArgs e) { if (sender is Window owner) { // 不需要手动关闭通知,因为设置了Owner会自动关闭 // 但需要取消订阅 UnsubscribeFromOwnerEvents(owner); } } private static void OnOwnerStateChanged(object sender, EventArgs e) { if (sender is Window owner) { var notifications = GetNotificationListForOwner(owner); var visibility = (owner.WindowState == WindowState.Minimized) ? Visibility.Collapsed : Visibility.Visible; foreach (var list in notifications.Values) { foreach (var notificationView in list) { notificationView.Visibility = visibility; } } // 恢复时重新定位 if (visibility == Visibility.Visible) { RepositionAll(owner); } } } private static void OnOwnerMoved(object sender, EventArgs e) { if (sender is Window owner) { RepositionAll(owner); } } private static void RepositionAll(Window owner) { Reposition(owner, NotificationPlacement.TopRight); Reposition(owner, NotificationPlacement.TopLeft); Reposition(owner, NotificationPlacement.BottomRight); Reposition(owner, NotificationPlacement.BottomLeft); } private static void Reposition(Window owner, NotificationPlacement placement) { var activeNotifications = GetNotificationListForOwner(owner); var notifications = activeNotifications[placement]; if (notifications.Count == 0) return; // 获取定位基准区域 Rect workArea; if (owner != null && owner.IsLoaded) { // 如果父窗口最小化,则不进行重定位 if (owner.WindowState == WindowState.Minimized) return; workArea = new Rect(owner.Left, owner.Top, owner.ActualWidth, owner.ActualHeight); } else { workArea = SystemParameters.WorkArea; } double currentTop = workArea.Top + 10; double currentBottom = workArea.Bottom - 10; foreach (var window in notifications) { // 确保窗口已加载以获取其实际尺寸 if (!window.IsLoaded) { window.WindowStartupLocation = WindowStartupLocation.Manual; window.Left = -9999; window.Top = -9999; window.Show(); window.Activate(); // 激活以确保尺寸计算完成 } if (placement == NotificationPlacement.TopRight || placement == NotificationPlacement.TopLeft) { window.Top = currentTop; currentTop += window.ActualHeight + 10; } else // BottomRight or BottomLeft { window.Top = currentBottom - window.ActualHeight; currentBottom -= (window.ActualHeight + 10); } if (placement == NotificationPlacement.TopRight || placement == NotificationPlacement.BottomRight) { window.Left = workArea.Right - window.ActualWidth - 10; } else // TopLeft or BottomLeft { window.Left = workArea.Left + 10; } } } }