Files

168 lines
4.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
namespace _29WxOrderWpf.ToolCtrls;
public enum ToastType { Info, Success, Warning, Error }
/// <summary>
/// 在指定 Window 内显示自动消失的提示框(Toast)
/// 用法:ToastHelper.Show(window, "保存成功", ToastType.Success);
/// </summary>
public static class ToastHelper
{
// 每个 Window 对应一个提示容器,用于多个提示堆叠
private static readonly ConditionalWeakTable<Window, ToastAdorner> Hosts = new();
private static SolidColorBrush msgBrush;
static ToastHelper()
{
msgBrush = Application.Current.Resources["pageColorBrush"] as SolidColorBrush;
msgBrush = Application.Current.Resources["pageColorBrush"] as SolidColorBrush;
}
/// <param name="owner">要展示提示的窗口</param>
/// <param name="message">提示内容</param>
/// <param name="type">类型(决定背景色)</param>
/// <param name="durationMs">停留时长(毫秒,不含淡入淡出)</param>
public static void Show(Window owner, string message,
ToastType type = ToastType.Info, int durationMs = 2000)
{
if (owner == null || string.IsNullOrEmpty(message)) return;
// 允许在非 UI 线程调用
if (!owner.Dispatcher.CheckAccess())
{
owner.Dispatcher.BeginInvoke(new Action(() => Show(owner, message, type, durationMs)));
return;
}
// 窗口还没加载完,等加载后再显示
if (!owner.IsLoaded)
{
RoutedEventHandler handler = null;
handler = (s, e) =>
{
owner.Loaded -= handler;
Show(owner, message, type, durationMs);
};
owner.Loaded += handler;
return;
}
if (owner.Content is not UIElement content) return;
var layer = AdornerLayer.GetAdornerLayer(content);
if (layer == null) return;
if (!Hosts.TryGetValue(owner, out var host))
{
host = new ToastAdorner(content);
layer.Add(host);
Hosts.Add(owner, host);
}
var toast = CreateToast(message, type);
host.Panel.Children.Add(toast);
// 淡入 -> 停留 -> 淡出
var fadeIn = TimeSpan.FromMilliseconds(250);
var fadeOut = TimeSpan.FromMilliseconds(400);
var hold = TimeSpan.FromMilliseconds(Math.Max(durationMs, 0));
var anim = new DoubleAnimationUsingKeyFrames();
anim.KeyFrames.Add(new LinearDoubleKeyFrame(0, TimeSpan.Zero));
anim.KeyFrames.Add(new LinearDoubleKeyFrame(1, fadeIn));
anim.KeyFrames.Add(new LinearDoubleKeyFrame(1, fadeIn + hold));
anim.KeyFrames.Add(new LinearDoubleKeyFrame(0, fadeIn + hold + fadeOut));
anim.Completed += (s, e) =>
{
host.Panel.Children.Remove(toast);
if (host.Panel.Children.Count == 0)
{
layer.Remove(host);
Hosts.Remove(owner);
}
};
toast.BeginAnimation(UIElement.OpacityProperty, anim);
}
public static void Show(string message,
ToastType type = ToastType.Info, int durationMs = 2000)
{
Window window = Application.Current.Windows
.OfType<Window>()
.FirstOrDefault(w => w.IsActive) ?? Application.Current.MainWindow;
Show(window, message, type, durationMs);
}
private static Border CreateToast(string message, ToastType type)
{
var color = type switch
{
ToastType.Success => Color.FromRgb(0x2E, 0x9E, 0x5B),
ToastType.Warning => Color.FromRgb(0xE6, 0x9A, 0x1A),
ToastType.Error => Color.FromRgb(0xD9, 0x4A, 0x4A),
_ => msgBrush.Color,//Color.FromRgb(0x40, 0x40, 0x40),
};
return new Border
{
Background = new SolidColorBrush(color),
CornerRadius = new CornerRadius(6),
Padding = new Thickness(18, 10, 18, 10),
Margin = new Thickness(0, 0, 0, 8),
Opacity = 0,
MaxWidth = 480,
Effect = new DropShadowEffect
{
BlurRadius = 12,
ShadowDepth = 2,
Opacity = 0.3,
Color = Colors.Black
},
Child = new TextBlock
{
Text = message,
Foreground = Brushes.White,
FontSize = 14,
TextWrapping = TextWrapping.Wrap
}
};
}
/// <summary>覆盖在窗口内容上方的提示容器,顶部居中</summary>
private sealed class ToastAdorner : Adorner
{
public StackPanel Panel { get; }
public ToastAdorner(UIElement adornedElement) : base(adornedElement)
{
IsHitTestVisible = false; // 不拦截鼠标事件
Panel = new StackPanel { Orientation = Orientation.Vertical };
AddVisualChild(Panel);
}
protected override int VisualChildrenCount => 1;
protected override Visual GetVisualChild(int index) => Panel;
protected override Size MeasureOverride(Size constraint)
{
Panel.Measure(constraint);
return AdornedElement.RenderSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
var size = Panel.DesiredSize;
double x = (finalSize.Width - size.Width) / 2;
Panel.Arrange(new Rect(new Point(Math.Max(x, 0), 30), size)); // 距顶部 30
return finalSize;
}
}
}