65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 吐司通知 - 深入研究。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (13投票s)

2016 年 2 月 11 日

MIT

6分钟阅读

viewsIcon

35089

downloadIcon

1428

WPF 应用程序的精美吐司通知, 易于使用并支持 MVVM 模式。

只需下载项目演示,使用 Nuget 恢复丢失的包即可尽情体验。 笑脸 | :)

引言

在本文中,我将完成我之前的文章 WPF 弹出通知。在那篇文章中,我们介绍了如何在 WPF 应用程序中使用弹出通知包。在本文中,我们将解释 WPFNotification 包的问题以及背后的代码。

Github

您可以从 这里 在 Github 上查看该项目,下载源代码和演示应用程序,尽情体验。 笑脸 | :)

演示展示了如何使用弹出通知,可以按默认实现显示,也可以按自定义实现显示。

Nuget

WPF 弹出通知可在 NuGet 上获取,您可以使用 NuGet 管理器安装它,或者在程序包管理器控制台中运行以下命令。

PM> Install-Package WPFNotification

问题

最近我需要在 WPF 应用程序中使用一个弹出通知来向用户显示一些信息。于是我搜索了 WPF 通知,看看是否有符合我需求的搜索结果,以及从哪里开始。我发现了一个名为 Elysium 的优秀开源项目。它具有通知框功能。但存在一些问题。

  • 通知的 UI 与我的应用程序 UI 不匹配。
  • 我需要显示许多通知,每个通知都有不同的 UI。
  • 它是一个大型库,而我不需要其他控件。
  • 我需要一次显示一个通知,任何其他通知都将被放入队列。

于是我从这里开始,并调整了通知框,试图使其成为一个轻量级且时尚的可重用组件,易于使用并符合我的需求。

使用代码

WPFNotification 项目结构包含六个文件夹

  • 模型
    • Notification.cs
  • 资产
    • CloseButton.xaml
    • NotificationItem.xaml
    • NotificationUI.xaml
  • 核心
    • 配置
      • NotificationConfiguration.cs
      • NotificationFlowDirection.cs
    • Interactivity
      • FadeBehavior.cs
      • SlideBehavior.cs
    • NotifyBox.cs
  • 服务
    • INotificationDialogService.cs
    • NotificationDialogService.cs
  • Converters
    • BaseConverter.cs
    • EmptyStringConverter.cs
  • 资源
    • 图像
      • notification-icon.png

现在我们将一步一步地探讨结构。所以我们将看看

模型

包含通知模型。它只有 Title、Message 和 Image URL

public class Notification
    {
        public string Title { get; set; }
        public string Message { get; set; }
        public string ImgURL { get; set; }
    }

资产

此文件夹包含通知数据模板,通知默认样式和关闭按钮样式。

  • CloseButton.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Style x:Key="SystemButtonBase" TargetType="ButtonBase">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Padding" Value="1"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ButtonBase}">
                    <Border Name="Chrome"
                                Background="{TemplateBinding Background}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                SnapsToDevicePixels="true">
                     <ContentPresenter Margin="{TemplateBinding Padding}"
                                       VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                       HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                       RecognizesAccessKey="True"
                                       SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    <Style x:Key="SystemButton" TargetType="ButtonBase" BasedOn="{StaticResource SystemButtonBase}">
        <Setter Property="Width" Value="32" />
        <Setter Property="Height" Value="24" />
        <Setter Property="Foreground" Value="#d1d1d1"/>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="#3e3e42" />
                <Setter Property="Foreground" Value="#d1d1d1"/>
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter Property="Background" Value="#1ba1e2" />
                <Setter Property="Foreground" Value="#d1d1d1" />
            </Trigger>
            <Trigger Property="IsEnabled" Value="false">
                <Setter Property="Foreground" Value="#515151" />
            </Trigger>
        </Style.Triggers>
    </Style>

    <Style x:Key="SystemCloseButton" TargetType="ButtonBase" BasedOn="{StaticResource SystemButton}">
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="#3e3e42" />
                <Setter Property="Foreground" Value="#d1d1d1"/>
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter Property="Background" Value="#e51400" />
                <Setter Property="Foreground" Value="White" />
            </Trigger>
            <Trigger Property="IsEnabled" Value="false">
                <Setter Property="Foreground" Value="#515151" />
            </Trigger>
        </Style.Triggers>
    </Style>
    
</ResourceDictionary>
  • NotificationItem.xaml 
<UserControl x:Class="WPFNotification.Assets.NotificationItem"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:converters="clr-namespace:WPFNotification.Converters"
             mc:Ignorable="d"
             d:DesignHeight="150" d:DesignWidth="300"
             x:Name="NotificationWindow"
             Background="Transparent">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/WPFNotification;component/Assets/CloseButton.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid Background="Transparent">
        <Border Name="border" Background="#2a3345" BorderThickness="0" CornerRadius="10" Margin="10">
            <Border.Effect>
                <DropShadowEffect ShadowDepth="0" Opacity="0.8" BlurRadius="10"/>
            </Border.Effect>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"></RowDefinition>
                    <RowDefinition Height="*"></RowDefinition>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                    <ColumnDefinition Width="*"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <Image Grid.RowSpan="2"
                       Source="{Binding ImgURL, Converter={converters:EmptyStringConverter}, ConverterParameter='pack://application:,,,/WPFNotification;component/Resources/Images/notification-icon.png'}"
                       Margin="4" Width="80"></Image>
                <TextBlock Grid.Column="1" Text="{Binding Path=Title}"  TextOptions.TextRenderingMode="ClearType" TextOptions.TextFormattingMode="Display" Foreground="White"
                                   FontFamily="Arial" FontSize="14" FontWeight="Bold" VerticalAlignment="Center"  Margin="2,4,4,2" TextWrapping="Wrap" TextTrimming="CharacterEllipsis" />
                <Button x:Name="CloseButton"
                        Width="16"
                        Height="16"
                        Grid.Column="1"
                        HorizontalAlignment="Right"
                        Margin="0,0,12,0"
                        Click="CloseButton_Click"
                        Style="{StaticResource SystemCloseButton}">
                    <Button.Content>
                        <Grid Width="10" Height="12" RenderTransform="1,0,0,1,0,1">
                            <Path Data="M0,0 L8,7 M8,0 L0,7 Z" Width="8" Height="7"
                             VerticalAlignment="Center" HorizontalAlignment="Center"
                            Stroke="{Binding Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}"
                            StrokeThickness="1.5"  />
                        </Grid>
                    </Button.Content>
                </Button>
                <TextBlock Grid.Row="1"
                           Grid.Column="1"
                           Text="{Binding Path=Message}"
                           TextOptions.TextRenderingMode="ClearType"
                           TextOptions.TextFormattingMode="Display"
                           Foreground="White"
                           FontFamily="Arial"
                           VerticalAlignment="Stretch"  
                           Margin="5"
                           TextWrapping="Wrap"
                           TextTrimming="CharacterEllipsis"/>
            </Grid>
        </Border>
    </Grid>
</UserControl>

在此文件中,我们仅创建默认通知窗口并将其绑定到通知模型。窗口包含

  • 一个图像以显示通知图像。如果 notification ImageURL 为空,它将显示 images 文件夹中的默认预定义图像,这要归功于 EmptyStringConverter
  • 两个文本框,一个用于显示通知标题,另一个用于显示通知消息。
  • 关闭按钮,用于立即关闭通知窗口。要实现此功能,我们需要实现 CloseButton_Click 事件。
private void CloseButton_Click(object sender, RoutedEventArgs e)
        {
            Window parentWindow = Window.GetWindow(this);
            this.Visibility = Visibility.Hidden;
            parentWindow.Close();
        }
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:Model="clr-namespace:WPFNotification.Model"
                    xmlns:NotificationView="clr-namespace:WPFNotification.Assets">
       
    <DataTemplate x:Key="notificationTemplate" DataType="{x:Type Model:Notification}">
        <NotificationView:NotificationItem/>
    </DataTemplate>

</ResourceDictionary>

一个资源字典,其中包含 DataTemplate 以定义通知模型的呈现。我们将使用它的名称作为配置对象的默认值。您必须在 App.xaml 文件中引用此文件。您可以在 WPF 弹出通知文章 - 入门部分 中看到这一点。

核心

配置

它包含

  1. NotificationConfiguration
  2. NotificationFlowDirection 枚举。

NotificationConfiguration

 public class NotificationConfiguration
    {
        #region Configuration Default values
        /// <summary>
        /// The default display duration for a notification window.
        /// </summary>
        private static readonly TimeSpan DefaultDisplayDuration = TimeSpan.FromSeconds(2);

        /// <summary>
        /// The default notifications window Width
        /// </summary>
        private const int DefaultWidth = 300;

        /// <summary>
        /// The default notifications window Height
        /// </summary>
        private const int DefaultHeight = 150;

        /// <summary>
        /// The default template of notification window
        /// </summary>
        private const string DefaultTemplateName = "notificationTemplate";
        #endregion

        #region constructor
        /// <summary>
        /// Initialises the configuration object.
        /// </summary>
        /// <param name="displayDuration">The notification display duration. set it TimeSpan.
        ///                               Zero to use default value </param>
        /// <param name="width">The notification width. set it to null to use default value</param>
        /// <param name="height">The notification height. set it to null to use default value</param>
        /// <param name="templateName">The notification template name. 
        ///                            set it to null to use default value</param>
        /// <param name="notificationFlowDirection">The notification flow direction. 
        ///       set it to null to use default value (RightBottom)</param>
        public NotificationConfiguration(TimeSpan displayDuration, int? width, int? height,
                                         string templateName,
                                         NotificationFlowDirection? notificationFlowDirection)
        {
            DisplayDuration = displayDuration > TimeSpan.Zero ? displayDuration : DefaultDisplayDuration;
            Width = width.HasValue ? width : DefaultWidth;
            Height = height.HasValue ? height : DefaultHeight;
            TemplateName = !string.IsNullOrEmpty(templateName) ? templateName : DefaultTemplateName;
            NotificationFlowDirection = notificationFlowDirection ?? NotificationFlowDirection.RightBottom;
        }
        #endregion

        #region public Properties
        /// <summary>
        /// The default configuration object
        /// </summary>
        public static NotificationConfiguration DefaultConfiguration
        {
            get
            {
                return new NotificationConfiguration(DefaultDisplayDuration,DefaultWidth,
                                                     DefaultHeight, DefaultTemplateName, 
                                                     NotificationFlowDirection.RightBottom);
            }
        }

        /// <summary>
        /// The display duration for a notification window.
        /// </summary>
        public TimeSpan DisplayDuration { get; private set; }

        /// <summary>
        /// Notifications window Width
        /// </summary>
        public int? Width { get; private set; }

        /// <summary>
        /// Notifications window Height
        /// </summary>
        public int? Height { get; private set; }

        /// <summary>
        /// The template of notification window
        /// </summary>
        public string TemplateName { get; private set; }

        /// <summary>
        /// The notification window flow direction
        /// </summary>
        public NotificationFlowDirection NotificationFlowDirection { get; set; }

        #endregion
    }

使用此类,您可以配置通知

  • 宽度。默认值为 300
  • 高度。默认值为 150
  • 显示时长。默认值为 2 秒。
  • 模板名称。默认值为“notificationTemplate”,此值是 NotificationUI.xaml 文件中数据模板的名称。
  • NotificationFlowDirection.  这设置了新通知窗口出现的方向。默认值为 RightBottom

NotificationFlowDirection

代表通知窗口方向的枚举

 public enum NotificationFlowDirection
    {
        RightBottom,
        LeftBottom,
        LeftUp,
        RightUp,
    }

Interactivity

包含两个行为

  • 用于通知窗口淡入/淡出的淡入淡出行为
  • 用于通知窗口滑动进出/出的滑动行为

NotifyBox

在我们开始解释 NotifyBox 类之前,让我先介绍一下 WindowInfo 类。此类将保存窗口的元数据。

 private sealed class WindowInfo
        {
            public int ID { get; set; }
    
            public TimeSpan DisplayDuration { get; set; }

            public Window Window { get; set; }
        }

NotifyBox  是一个依赖于 Reactive Extensions 的静态类。它包含一些私有字段

public static class NotifyBox
    {
        private const int MAX_NOTIFICATIONS = 1;
       
        private static int notificationWindowsCount;
       
        private const double Margin = 5;

        private static List<WindowInfo> notificationWindows;

        private static List<WindowInfo> notificationsBuffer;

        static NotifyBox()
        {
            notificationWindows = new List<WindowInfo>();
            notificationsBuffer = new List<WindowInfo>();
            notificationWindowsCount = 0;
        }
.......
.......
}
  • MAX_NOTIFICATIONS 是您希望同时显示的通知数量。

注意:在当前版本中,MAX_NOTIFICATIONS 设置为一,且不可配置,我们一次只显示一个通知。

  • notificationWindowsCount 一个累积数字,表示通知的数量

  • notificationWindows 要显示的通知列表

  • notificationsBuffer 一个缓冲区列表,将放置待处理的通知。

现在让我们解释公共方法

public static class NotifyBox
{
.....
.....      
        public static void Show(object content, NotificationConfiguration configuration)
        {
            DataTemplate notificationTemplate = (DataTemplate)Application.Current.Resources[configuration.TemplateName];
            Window window = new Window()
            {
                Title = "",
                Width = configuration.Width.Value,
                Height = configuration.Height.Value,
                Content = content,
                ShowActivated = false,
                AllowsTransparency = true,
                WindowStyle = WindowStyle.None,
                ShowInTaskbar = false,
                Topmost = true,
                Background = Brushes.Transparent,
                UseLayoutRounding = true,
                ContentTemplate = notificationTemplate
            };
            Show(window, configuration.DisplayDuration);
        }

        public static void Show(object content)
        {
            Show(content, NotificationConfiguration.DefaultConfiguration);
        }

        public static void Show(Window window, TimeSpan displayDuration, 
                               NotificationFlowDirection notificationFlowDirection )
        {
            BehaviorCollection behaviors = Interaction.GetBehaviors(window);
            behaviors.Add(new FadeBehavior());
            behaviors.Add(new SlideBehavior());
            SetWindowDirection(window, notificationFlowDirection);
            notificationWindowsCount += 1;
            WindowInfo windowInfo = new WindowInfo()
            {
                ID = notificationWindowsCount,
                DisplayDuration = displayDuration,
                Window = window
            };
            
            if (notificationWindows.Count + 1 > MAX_NOTIFICATIONS)
            {
                notificationsBuffer.Add(windowInfo);
            }
            else
            {
                Observable
              .Timer(displayDuration)
              .ObserveOnDispatcher()
              .Subscribe(x => OnTimerElapsed(windowInfo));
                notificationWindows.Add(windowInfo);
                window.Show();
            }
        }

        /// <summary>
        /// Remove all notifications from notification list and buffer list.
        /// </summary>
        public static void ClearNotifications()
        {
            notificationWindows.Clear();
            notificationsBuffer.Clear();
            notificationWindowsCount = 0;
        }

......
......

我们有 ClearNotifications  方法

在此方法中,我们只是将所有内容重置为零状态

  • 我们将 notificationWindowsCount 设置为零。
  • 我们清空 notificationWindows 和 notificationsBuffer  列表。

然后我们有三个重载的 Show 方法

public static void Show(object content)
public static void Show(object content, NotificationConfiguration configuration)

在此方法中,我们执行以下步骤

  • 使用 configuration 对象中的 templateName ,获取 notificationTemplate
  • 使用此模板和配置对象的其他值创建 window 对象。
  • 调用 Show 方法,并传入 window 对象和配置的显示时长值。
 public static void Show(Window window, TimeSpan displayDuration, NotificationFlowDirection notificationFlowDirection)

在此方法中,我们执行以下步骤

  • 我们获取与 window 对象关联的 BehaviorCollection ,并向其中添加 slidefade 行为。
  • 我们调用 SetWindowDirection 方法,根据 notificationFlowDirection 值将通知窗口放置在正确的位置,默认值为屏幕的 (RightBottom) 角。
  • 我们创建一个 WindowInfo 对象,并检查当前显示的通知数量是否大于 MAX_NOTIFICATIONS。如果是,我们将此通知添加到缓冲区列表,否则我们显示通知。显示时长结束后,我们调用回调函数 OnTimerElapsed

现在让我们讨论 OnTimerElapsed 方法

  private static void OnTimerElapsed(WindowInfo windowInfo)
        {
            if (windowInfo.Window.IsMouseOver)
            {
                Observable
                    .Timer(windowInfo.DisplayDuration)
                    .ObserveOnDispatcher()
                    .Subscribe(x => OnTimerElapsed(windowInfo));
            }
            else
            {
                BehaviorCollection behaviors = Interaction.GetBehaviors(windowInfo.Window);
                FadeBehavior fadeBehavior = behaviors.OfType<FadeBehavior>().First();
                SlideBehavior slideBehavior = behaviors.OfType<SlideBehavior>().First();

                fadeBehavior.FadeOut();
                slideBehavior.SlideOut();

                EventHandler eventHandler = null;
                eventHandler = (sender2, e2) =>
                {
                    fadeBehavior.FadeOutCompleted -= eventHandler;
                    notificationWindows.Remove(windowInfo);
                    windowInfo.Window.Close();

                    if (notificationsBuffer != null && notificationsBuffer.Count > 0)
                    {
                        var BufferWindowInfo = notificationsBuffer.First();
                        Observable
                         .Timer(BufferWindowInfo.DisplayDuration)
                         .ObserveOnDispatcher()
                         .Subscribe(x => OnTimerElapsed(BufferWindowInfo));
                        notificationWindows.Add(BufferWindowInfo);
                        BufferWindowInfo.Window.Show();
                        notificationsBuffer.Remove(BufferWindowInfo);
                    }
                };
                fadeBehavior.FadeOutCompleted += eventHandler;
            }
        }

通知显示时长结束后将调用此方法。在此方法中,我们检查鼠标指针是否仍然位于通知窗口上方。在某些情况下

  • ,我们将通知窗口再显示一个显示时长。
  • ,我们获取窗口的相关行为,然后调用 FadeOut()SlideOut 方法,并订阅 FadeOutCompleted 回调函数。在此方法中
    • 我们关闭通知窗口,并将其从显示的通知列表中移除。
    • 如果缓冲区列表(队列)不为空,我们从队列中取出第一个通知并在屏幕上显示它。

最后,在 NotifyBox 类中,让我们讨论 SetWindowDirection 方法

 private static void SetWindowDirection(Window window, NotificationFlowDirection notificationFlowDirection)
        {
            var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
            var transform = PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget.TransformFromDevice;
            var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom));

            switch (notificationFlowDirection)
            {
                case NotificationFlowDirection.RightBottom:
                    window.Left = corner.X - window.Width - window.Margin.Right - Margin;
                    window.Top = corner.Y - window.Height - window.Margin.Top;
                    break;
                case NotificationFlowDirection.LeftBottom:
                    window.Left = 0;
                    window.Top = corner.Y - window.Height - window.Margin.Top;
                    break;
                case NotificationFlowDirection.LeftUp:
                    window.Left = 0;
                    window.Top = 0;
                    break;
                case NotificationFlowDirection.RightUp:
                    window.Left = corner.X - window.Width - window.Margin.Right - Margin;
                    window.Top = 0;
                    break;
                default:
                    window.Left = corner.X - window.Width - window.Margin.Right - Margin;
                    window.Top = corner.Y - window.Height - window.Margin.Top;
                    break;
            }
        }

在此方法中,我们仅计算通知窗口的坐标,以便根据 notificationFlowDirection 值在正确的位置显示通知,而不考虑监视器分辨率或计算机是否使用默认 DPI 设置。有关 WPF 中 DPI 值的更多信息,请参阅这篇 文章

服务

包含一个 INotificationDialogService 接口,该接口有两个方法

public interface INotificationDialogService
    {
        /// <summary>
        /// Show notification window.
        /// </summary>
        /// <param name="content">The notification object.</param>
        void ShowNotificationWindow(object content);

        /// <summary>
        /// Show notification window.
        /// </summary>
        /// <param name="content">The notification object.</param>
        /// <param name="configuration">The notification configuration object.</param>
        void ShowNotificationWindow(object content, NotificationConfiguration configuration);
  
        /// <summary>
        /// Remove all notifications from notification list and buffer list.
        /// </summary>
        void ClearNotifications();
    }

要显示通知窗口,您必须在项目中实现此接口。或者使用默认实现。让我们看看 NotificationDialogService;在此类中,我们仅实现 ShowNotificationWindow 方法。我认为代码是不言自明的。

 public class NotificationDialogService : INotificationDialogService
    {
        /// <summary>
        /// Show notification window.
        /// </summary>
        /// <param name="content">The notification object.</param>
        public void ShowNotificationWindow(object content)
        {
            NotifyBox.Show(content);
        }

        /// <summary>
        /// Show notification window.
        /// </summary>
        /// <param name="content">The notification object.</param>
        /// <param name="configuration">The notification configuration object.</param>
        public void ShowNotificationWindow(object content, NotificationConfiguration configuration)
        {
            NotifyBox.Show(content, configuration);
        }

        /// <summary>
        ///  Remove all notifications from notification list and buffer list.
        /// </summary>
        public void ClearNotifications()
        {
            NotifyBox.ClearNotifications();
        }
    }

 

Converters

包含 EmptyStringConverter,当将通知图像绑定到通知对象中的 ImgURL 属性时,我们使用此转换器。因此,如果 ImageURL 为空,转换器将分配默认图像 URL。

 public class EmptyStringConverter : BaseConverter, IValueConverter
    {
        public EmptyStringConverter()
        { }
        public object Convert(object value, Type targetType,
                              object parameter, CultureInfo culture)
        {
            return string.IsNullOrEmpty(value as string) ? parameter : value;
        }

        public object ConvertBack(object value, Type targetType,
                                  object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }


资源

它包含 Images 文件夹,其中包含默认通知图像。只要您 没有 覆盖 <span class="sac" id="spans0e3">notification</span> 对象中的 ImgURL 属性,该图像将用于通知 窗口

历史

  • 2016 年 2 月 12 日  发布了原始帖子。
  • 2016 年 7 月 3 日  添加了新功能
    • 添加了对 Windows 7 及更高版本的支持。
    • 添加了对在不同屏幕分辨率上显示通知的支持。
    • 添加了通知流方向。
    • 允许删除缓冲区列表中的所有通知

 

 

 

© . All rights reserved.