月更
This commit is contained in:
328
AntDesignWPF/Controls/Notification/NotificationManager.cs
Normal file
328
AntDesignWPF/Controls/Notification/NotificationManager.cs
Normal file
@@ -0,0 +1,328 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace AntDesignWPF.Controls;
|
||||
|
||||
public enum NotificationPlacement
|
||||
{
|
||||
TopRight,
|
||||
TopLeft,
|
||||
BottomRight,
|
||||
BottomLeft
|
||||
}
|
||||
|
||||
//public static class NotificationManager
|
||||
//{
|
||||
// private static readonly Dictionary<NotificationPlacement, List<NotificationView>> _activeNotifications = new Dictionary<NotificationPlacement, List<NotificationView>>
|
||||
// {
|
||||
// { NotificationPlacement.TopRight, new List<NotificationView>() },
|
||||
// { NotificationPlacement.TopLeft, new List<NotificationView>() },
|
||||
// { NotificationPlacement.BottomRight, new List<NotificationView>() },
|
||||
// { NotificationPlacement.BottomLeft, new List<NotificationView>() }
|
||||
// };
|
||||
|
||||
// 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<Window, Dictionary<NotificationPlacement, List<NotificationView>>> _windowNotifications =
|
||||
new System.Runtime.CompilerServices.ConditionalWeakTable<Window, Dictionary<NotificationPlacement, List<NotificationView>>>();
|
||||
|
||||
// 保留一个用于全局通知的静态字典(当 owner 为 null 时使用)
|
||||
private static readonly Dictionary<NotificationPlacement, List<NotificationView>> _globalNotifications =
|
||||
new Dictionary<NotificationPlacement, List<NotificationView>>
|
||||
{
|
||||
{ NotificationPlacement.TopRight, new List<NotificationView>() },
|
||||
{ NotificationPlacement.TopLeft, new List<NotificationView>() },
|
||||
{ NotificationPlacement.BottomRight, new List<NotificationView>() },
|
||||
{ NotificationPlacement.BottomLeft, new List<NotificationView>() }
|
||||
};
|
||||
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 显示一个通知。
|
||||
/// </summary>
|
||||
/// <param name="owner">通知要附着到的父窗口。如果为 null,则显示为全局通知。</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="type">类型</param>
|
||||
/// <param name="placement">位置</param>
|
||||
/// <param name="durationSeconds">显示时长(秒)</param>
|
||||
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<NotificationPlacement, List<NotificationView>> GetNotificationListForOwner(Window owner)
|
||||
{
|
||||
if (owner == null)
|
||||
{
|
||||
return _globalNotifications;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
return _windowNotifications.GetValue(owner, key =>
|
||||
{
|
||||
// 当 ConditionalWeakTable 中没有找到该窗口的条目时,此工厂方法被调用
|
||||
return new Dictionary<NotificationPlacement, List<NotificationView>>
|
||||
{
|
||||
{ NotificationPlacement.TopRight, new List<NotificationView>() },
|
||||
{ NotificationPlacement.TopLeft, new List<NotificationView>() },
|
||||
{ NotificationPlacement.BottomRight, new List<NotificationView>() },
|
||||
{ NotificationPlacement.BottomLeft, new List<NotificationView>() }
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user