112 lines
4.2 KiB
C#
112 lines
4.2 KiB
C#
namespace Melskin.Controls;
|
||
|
||
/// <summary>
|
||
/// 通知管理器类,提供显示系统通知的方法。
|
||
/// 该类主要用于在应用程序中方便地创建和显示不同类型的通知。
|
||
/// </summary>
|
||
public static class Notification
|
||
{
|
||
// 数据结构现在存储 Notification 模型,而不是 Window
|
||
private static readonly Dictionary<NotificationPlacement, List<Tuple<NotificationModel, NotificationView>>> ActiveNotifications =
|
||
new()
|
||
{
|
||
{ NotificationPlacement.TopRight, [] },
|
||
{ NotificationPlacement.TopLeft, [] },
|
||
{ NotificationPlacement.BottomRight, [] },
|
||
{ NotificationPlacement.BottomLeft, [] }
|
||
};
|
||
|
||
private static readonly object Lock = new();
|
||
|
||
/// <summary>
|
||
/// 显示系统通知。
|
||
/// </summary>
|
||
/// <param name="title">通知的标题。</param>
|
||
/// <param name="message">通知的内容消息。</param>
|
||
/// <param name="type">通知的类型,默认为信息类型。</param>
|
||
/// <param name="placement">通知在屏幕上的位置,默认为右上角。</param>
|
||
/// <param name="durationSeconds">通知显示的持续时间(秒),默认为5秒。</param>
|
||
public static void Show(string title, string message, NotificationType type = NotificationType.Info,
|
||
NotificationPlacement placement = NotificationPlacement.TopRight, int durationSeconds = 5)
|
||
{
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
var model = new NotificationModel(title, message, type, TimeSpan.FromSeconds(durationSeconds));
|
||
|
||
// 修改这里:在创建 view 时传入 placement
|
||
var view = new NotificationView(model, placement);
|
||
|
||
// 后续代码完全不变
|
||
view.Ready += (v) => OnNotificationReady(v, placement);
|
||
view.Closed += (_, _) => OnNotificationClosed(view, placement);
|
||
view.Show();
|
||
});
|
||
}
|
||
|
||
private static void OnNotificationReady(NotificationView view, NotificationPlacement placement)
|
||
{
|
||
lock (Lock)
|
||
{
|
||
ActiveNotifications[placement].Add(new Tuple<NotificationModel, NotificationView>(view.Model, view));
|
||
Reposition(placement);
|
||
}
|
||
}
|
||
|
||
private static void OnNotificationClosed(NotificationView view, NotificationPlacement placement)
|
||
{
|
||
lock (Lock)
|
||
{
|
||
var itemToRemove = ActiveNotifications[placement].FirstOrDefault(t => t.Item2 == view);
|
||
if (itemToRemove != null)
|
||
{
|
||
ActiveNotifications[placement].Remove(itemToRemove);
|
||
Reposition(placement);
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void Reposition(NotificationPlacement placement)
|
||
{
|
||
var notifications = ActiveNotifications[placement];
|
||
if (!notifications.Any()) return;
|
||
|
||
var workArea = SystemParameters.WorkArea;
|
||
double currentTop;
|
||
var isTop = placement == NotificationPlacement.TopLeft || placement == NotificationPlacement.TopRight;
|
||
|
||
if (isTop)
|
||
{
|
||
currentTop = workArea.Top + 10;
|
||
}
|
||
else // Bottom
|
||
{
|
||
// 对于底部,我们需要先计算总高度
|
||
var totalHeight = notifications.Sum(t => t.Item2.ActualHeight + 10);
|
||
currentTop = workArea.Bottom - totalHeight;
|
||
}
|
||
|
||
foreach (var tuple in notifications)
|
||
{
|
||
var view = tuple.Item2;
|
||
|
||
// 在 `Ready` 事件之后,ActualWidth/Height 已经是准确的了
|
||
var finalLeft = (placement == NotificationPlacement.TopRight || placement == NotificationPlacement.BottomRight)
|
||
? workArea.Right - view.ActualWidth - 10
|
||
: workArea.Left + 10;
|
||
|
||
// 检查这个窗口是否是新来的(通过透明度判断)
|
||
if (view.Opacity == 0)
|
||
{
|
||
// 如果是新的,命令它播放入场动画
|
||
view.AnimateIn(finalLeft, currentTop);
|
||
}
|
||
else
|
||
{
|
||
// 如果是已经存在的,命令它平滑移动到新位置
|
||
view.AnimateMove(currentTop);
|
||
}
|
||
|
||
currentTop += view.ActualHeight + 10; // 为下一个窗口计算Y坐标
|
||
}
|
||
}
|
||
} |