使用 .NET 在 Windows 中创建弹出“烤面包机”通知
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3034741/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Create popup "toaster" notifications in Windows with .NET
提问by Antony
I am using .NET and am creating a desktop app/service that will display notifications in the corner of my Desktop when certain events are triggered. I don't want to use a regular Message Box b/c that would be too intrusive. I want notifications to slide into view and then fade out after a few seconds. I am thinking of something that will act very much like the Outlook alerts that one gets when a new message arrives. The question is: Should I use WPF for this? I've never done anything with WPF but will happily try it if that's best means to the end. Is there a way to accomplish this with regular .NET libraries?
我正在使用 .NET 并且正在创建一个桌面应用程序/服务,当某些事件被触发时,它将在我的桌面的角落显示通知。我不想使用太麻烦的常规消息框 b/c。我希望通知滑入视图,然后在几秒钟后淡出。我正在考虑一些与收到新邮件时收到的 Outlook 警报非常相似的东西。问题是:我应该为此使用 WPF 吗?我从未对 WPF 做过任何事情,但如果这是最终的最佳方法,我会很乐意尝试。有没有办法用常规的 .NET 库来实现这一点?
回答by Ray Burns
WPF makes this absolutely trivial: It would proably take ten minutes or less. Here are the steps:
WPF 让这一切变得微不足道:它可能需要十分钟或更短的时间。以下是步骤:
- Create a Window, set AllowsTransparency="true" and add a Grid to it
- Set the Grid's RenderTransform to a ScaleTransform with origin of 0,1
- Create an animation on the grid that animates the ScaleX 0 to 1 then later animates the Opacity from 1 to 0
- In the constructor calculate Window.Top and Window.Left to place the window in the lower right-hand corner of the screen.
- 创建一个窗口,设置 AllowsTransparency="true" 并向其添加一个网格
- 将网格的 RenderTransform 设置为原点为 0,1 的 ScaleTransform
- 在网格上创建一个动画,使 ScaleX 0 到 1 动画,然后将不透明度从 1 动画化到 0
- 在构造函数中计算 Window.Top 和 Window.Left 以将窗口放置在屏幕的右下角。
That's all there is to it.
这里的所有都是它的。
Using Expression Blend it took about 8 minutes me to generate the following working code:
使用 Expression Blend 我花了大约 8 分钟来生成以下工作代码:
<Window
x:Class="NotificationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Notification Popup" Width="300" SizeToContent="Height"
WindowStyle="None" AllowsTransparency="True" Background="Transparent">
<Grid RenderTransformOrigin="0,1" >
<!-- Notification area -->
<Border BorderThickness="1" Background="Beige" BorderBrush="Black" CornerRadius="10">
<StackPanel Margin="20">
<TextBlock TextWrapping="Wrap" Margin="5">
<Bold>Notification data</Bold><LineBreak /><LineBreak />
Something just happened and you are being notified of it.
</TextBlock>
<CheckBox Content="Checkable" Margin="5 5 0 5" />
<Button Content="Clickable" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<!-- Animation -->
<Grid.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)">
<SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="0:0:2" Value="1"/>
<SplineDoubleKeyFrame KeyTime="0:0:4" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<Grid.RenderTransform>
<ScaleTransform ScaleY="1" />
</Grid.RenderTransform>
</Grid>
</Window>
With code behind:
后面有代码:
using System;
using System.Windows;
using System.Windows.Threading;
public partial class NotificationWindow
{
public NotificationWindow()
{
InitializeComponent();
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
{
var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom));
this.Left = corner.X - this.ActualWidth - 100;
this.Top = corner.Y - this.ActualHeight;
}));
}
}
Since WPF is one of the regular .NET libraries, the answer is yes, it ispossible to accomplish this with the "regular .NET libraries".
由于WPF是普通.NET库之一,答案是肯定的,这是可能的“常规.NET库”,以实现这一目标。
If you're asking if there is a way to do this without using WPF the answer is still yes, but it is extremely complex and will take more like 5 days than 5 minutes.
如果你问是否有办法在不使用 WPF 的情况下做到这一点,答案仍然是肯定的,但它非常复杂,需要 5 天而不是 5 分钟。
回答by LawMan
I went ahead and created a CodePlex site for this that includes "Toast Popups" and control "Help Balloons". These versions have more features than what's described below. https://toastspopuphelpballoon.codeplex.com.
我继续为此创建了一个 CodePlex 站点,其中包括“Toast Popups”和控制“Help Balloons”。这些版本具有比下面描述的更多的功能。 https://toastspopuphelpballoon.codeplex.com。
This was a great jumping off point for the solution that I was looking for. I've made a couple of modifications to meet my requirements:
这是我正在寻找的解决方案的一个很好的起点。我做了一些修改以满足我的要求:
- I wanted to stop the animation on mouse over.
- "Reset" animation when mouse leave.
- Close the Window when opacity reached 0.
- Stack the Toast (I have not solved the problem if the number of windows exceeds the screen height)
- Call Load from my ViewModel
- 我想在鼠标悬停时停止动画。
- 鼠标离开时“重置”动画。
- 当不透明度达到 0 时关闭窗口。
- Stack the Toast(如果窗口数超过屏幕高度我还没解决问题)
- 从我的 ViewModel 调用 Load
Here's my XAML
这是我的 XAML
<Window x:Class="Foundation.FundRaising.DataRequest.Windows.NotificationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NotificationWindow" Height="70" Width="300" ShowInTaskbar="False"
WindowStyle="None" AllowsTransparency="True"
Background="Transparent">
<Grid RenderTransformOrigin="0,1" >
<Border BorderThickness="2" Background="{StaticResource GradientBackground}" BorderBrush="DarkGray" CornerRadius="7">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image Grid.Column="0"
Grid.RowSpan="2"
Source="Resources/data_information.png"
Width="40" Height="40"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<Image Grid.Column="2"
Source="Resources/error20.png"
Width="20"
Height="20"
VerticalAlignment="Center"
ToolTip="Close"
HorizontalAlignment="Center"
Cursor="Hand" MouseUp="ImageMouseUp"/>
<TextBlock Grid.Column="1"
Grid.Row="0"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold" FontSize="15"
Text="A Request has been Added"/>
<Button Grid.Column="1"
Grid.Row="1"
FontSize="15"
Margin="0,-3,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="Click Here to View"
Style="{StaticResource LinkButton}"/>
</Grid>
</Border>
<!-- Animation -->
<Grid.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard x:Name="StoryboardLoad">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="0.0" To="1.0" Duration="0:0:2" />
<DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="1.0" To="0.0" Duration="0:0:8" BeginTime="0:0:5" Completed="DoubleAnimationCompleted"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<RemoveStoryboard BeginStoryboardName="StoryboardLoad"/>
<RemoveStoryboard BeginStoryboardName="StoryboardFade"/>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard x:Name="StoryboardFade">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="1.0" To="0.0" Duration="0:0:8" BeginTime="0:0:2" Completed="DoubleAnimationCompleted"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<Grid.RenderTransform>
<ScaleTransform ScaleY="1" />
</Grid.RenderTransform>
</Grid>
The Code Behind
背后的代码
public partial class NotificationWindow : Window
{
public NotificationWindow()
: base()
{
this.InitializeComponent();
this.Closed += this.NotificationWindowClosed;
}
public new void Show()
{
this.Topmost = true;
base.Show();
this.Owner = System.Windows.Application.Current.MainWindow;
this.Closed += this.NotificationWindowClosed;
var workingArea = Screen.PrimaryScreen.WorkingArea;
this.Left = workingArea.Right - this.ActualWidth;
double top = workingArea.Bottom - this.ActualHeight;
foreach (Window window in System.Windows.Application.Current.Windows)
{
string windowName = window.GetType().Name;
if (windowName.Equals("NotificationWindow") && window != this)
{
window.Topmost = true;
top = window.Top - window.ActualHeight;
}
}
this.Top = top;
}
private void ImageMouseUp(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
this.Close();
}
private void DoubleAnimationCompleted(object sender, EventArgs e)
{
if (!this.IsMouseOver)
{
this.Close();
}
}
}
The call from the ViewModel:
来自 ViewModel 的调用:
private void ShowNotificationExecute()
{
App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() =>
{
var notify = new NotificationWindow();
notify.Show();
}));
}
The Styles referenced in the XAML:
XAML 中引用的样式:
<Style x:Key="LinkButton" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<TextBlock>
<ContentPresenter />
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextDecorations="Underline" Text="{TemplateBinding Content}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<LinearGradientBrush x:Key="GradientBackground" EndPoint="0.504,1.5" StartPoint="0.504,0.03">
<GradientStop Color="#FFFDD5A7" Offset="0"/>
<GradientStop Color="#FFFCE79F" Offset="0.567"/>
</LinearGradientBrush>
UPDATE:I added this event handler when the form is closed to "drop" the other windows.
更新:当表单关闭以“放下”其他窗口时,我添加了此事件处理程序。
private void NotificationWindowClosed(object sender, EventArgs e)
{
foreach (Window window in System.Windows.Application.Current.Windows)
{
string windowName = window.GetType().Name;
if (windowName.Equals("NotificationWindow") && window != this)
{
// Adjust any windows that were above this one to drop down
if (window.Top < this.Top)
{
window.Top = window.Top + this.ActualHeight;
}
}
}
}
回答by Anees Deen
public partial class NotificationWindow : Window
{
DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
public NotificationWindow()
: base()
{
this.InitializeComponent();
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
{
var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom));
this.Left = corner.X - this.ActualWidth;
this.Top = corner.Y - this.ActualHeight;
}));
timer.Interval = TimeSpan.FromSeconds(4d);
timer.Tick += new EventHandler(timer_Tick);
}
public new void Show()
{
base.Show();
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
//set default result if necessary
timer.Stop();
this.Close();
}
}
The above code is refined version @Ray Burns approach. Added with time interval code. So that Notification window would close after 4 seconds..
上面的代码是@Ray Burns 方法的精炼版。添加了时间间隔代码。这样通知窗口将在 4 秒后关闭..
Call the Window as,
将窗口称为,
NotificationWindow nfw = new NotificationWindow();
nfw.Show();
回答by Mehul Sant
NotifyIcon notifyIcon = new NotifyIcon();
Stream iconStream = System.Windows.Application.GetResourceStream(new Uri("pack://application:,,,/Assets/ic_instant_note_tray.ico")).Stream;
notifyIcon.Icon = new System.Drawing.Icon(iconStream);
notifyIcon.Text = string.Format(Properties.Resources.InstantNoteAppName, Constants.Application_Name);
notifyIcon.Visible = true;
notifyIcon.ShowBalloonTip(5000, "tooltiptitle", "tipMessage", ToolTipIcon.Info);
notifyIcon.Visible = false;
notifyIcon.Dispose();
回答by user1367200
I used the answer above to design my own notification window that is a bit more user friendly, in my opinion, and uses a few techniques that took me a little while to figure out. Sharing in case it helps anyone else out there.:
我使用上面的答案来设计我自己的通知窗口,在我看来,它对用户更加友好,并使用了一些让我花了一些时间才弄清楚的技术。分享以防它可以帮助其他人。:
- Added MouseEnter event trigger to immediately set window opacity to 1 so a user does not have to wait for the window to fade into full view.
- Added MouseLeave event trigger to fade the window opacity to 0 when the user moves the mouse out of the window.
- Added MouseUp (mouse click) event trigger to immediately set window opacity to 0 to hide the notification window.
- If you have to Show() and Hide() the notification window multiple times, then below method also resets the Storyboard at the end so that the next Show() operation starts the operation from the start and there is no glitch in the animation.
- 添加了 MouseEnter 事件触发器以立即将窗口不透明度设置为 1,因此用户不必等待窗口淡入全视图。
- 添加了 MouseLeave 事件触发器,当用户将鼠标移出窗口时,将窗口不透明度淡化为 0。
- 添加了 MouseUp(鼠标单击)事件触发器以立即将窗口不透明度设置为 0 以隐藏通知窗口。
- 如果您必须多次 Show() 和 Hide() 通知窗口,那么下面的方法还会在最后重置 Storyboard,以便下一个 Show() 操作从头开始操作并且动画中没有毛刺。
XAML:
XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ToastNotificationWindow"
Title="Notification Popup"
Width="480"
Height="140"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
BorderThickness="0"
Topmost="True"
>
<Grid Background="Transparent" Name="ToastWindowGrid" RenderTransformOrigin="0,1">
<Border Name="ToastWindowBorder" BorderThickness="0" Background="#333333">
<StackPanel Name="ToastWindowStackPanel" Margin="10" Orientation="Horizontal">
<Image Name="ToastLogo" Width="100" Height="100" Source="D:\Development\ToastNotification\resources\Logo-x100.png"/>
<StackPanel Name="ToastMessageStackPanel" Width="359">
<TextBox Name="ToastTitleTextBox" Margin="5" MaxWidth="340" Background="#333333" BorderThickness="0" IsReadOnly="True" Foreground="White" FontSize="20" Text="Toast Title" FontWeight="Bold" HorizontalContentAlignment="Center" Width="Auto" HorizontalAlignment="Stretch" IsHitTestVisible="False"/>
<TextBox Name="ToastMessageTextBox" Margin="5" MaxWidth="340" Background="#333333" BorderThickness="0" IsReadOnly="True" Foreground="LightGray" FontSize="16" Text="Toast title message. Click to start something." HorizontalContentAlignment="Left" TextWrapping="Wrap" IsHitTestVisible="False"/>
</StackPanel>
</StackPanel>
</Border>
<Grid.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<EventTrigger.Actions>
<BeginStoryboard Name="StoryboardLoad">
<Storyboard Name="ToastAnimationStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)" FillBehavior="HoldEnd">
<SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd">
<SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd">
<SplineDoubleKeyFrame KeyTime="0:0:20" Value="1"/>
<SplineDoubleKeyFrame KeyTime="0:0:23" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<StopStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/>
<BeginStoryboard Name="StoryboardMouseEnterFadeIn">
<Storyboard Name="ToastAnimationStoryboardMouseEnterFadeIn">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd">
<DiscreteDoubleKeyFrame KeyTime="0:0:0.1" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<EventTrigger.Actions>
<RemoveStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/>
<BeginStoryboard Name="StoryboardMouseLeaveFadeOut">
<Storyboard Name="ToastAnimationStoryboardMouseLeaveFadeOut">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd">
<SplineDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
<SplineDoubleKeyFrame KeyTime="0:0:3" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseUp">
<EventTrigger.Actions>
<StopStoryboard BeginStoryboardName="StoryboardMouseEnterFadeIn"/>
<RemoveStoryboard BeginStoryboardName="StoryboardMouseEnterFadeIn"/>
<StopStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/>
<RemoveStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/>
<BeginStoryboard Name="StoryboardMouseClickFadeOut">
<Storyboard Name="ToastAnimationStoryboardMouseClickFadeOut">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd">
<DiscreteDoubleKeyFrame KeyTime="0:0:0.1" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
<SeekStoryboard BeginStoryboardName="StoryboardLoad"/>
<PauseStoryboard BeginStoryboardName="StoryboardLoad"/>
</EventTrigger.Actions>
</EventTrigger>
</Grid.Triggers>
<Grid.RenderTransform>
<ScaleTransform ScaleY="1" />
</Grid.RenderTransform>
</Grid>
</Window>
Code behind:
后面的代码:
using System;
using System.Windows;
using System.Windows.Threading;
public partial class ToastNotificationWindow
{
public ToastNotificationWindow()
{
InitializeComponent();
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
{
var workingArea = System.Windows.SystemParameters.WorkArea;
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom));
this.Left = corner.X - this.ActualWidth - 10;
this.Top = corner.Y - this.ActualHeight;
}));
}
}
回答by PKBhaiya
Note that the calling thread must be sta because many ui components require this, while writing following code under system.timers.timer elapsed event
注意调用线程必须是sta,因为很多ui组件都需要这个,在system.timers.timer elapsed event下写如下代码
Window1 notifyWin = new Window1();
bool? isOpen = notifyWin.ShowDialog();
if (isOpen != null && isOpen == true)
{
notifyWin.Close();
}
System.Threading.Thread.Sleep(1000);
notifyWin.ShowDialog();
under window1 constructor:
在 window1 构造函数下:
public Window1()
{
InitializeComponent();
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => {
var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom));
this.Left = corner.X - this.ActualWidth - 100;
this.Top = corner.Y - this.ActualHeight;
}));
}

