使用.NET在Windows中创buildpopup式“烤面包机”通知

我正在使用.NET,并创build一个桌面应用程序/服务,当某些事件触发时,将在我的桌面的angular落显示通知。 我不想使用一个定期的消息框b / c,这太干扰了。 我想要通知滑入视图,然后几秒钟后淡出。 我正在考虑一些非常类似于当新消息到达时得到的Outlook警报。 问题是:我应该使用WPF吗? 我从来没有用WPF做过任何事情,但是如果这是最好的手段,我会很乐意尝试。 有没有一种方法来完成这与常规的.NET库?

WPF使这一切变得非常微不足道:这将花费十分钟或更less的时间。 这里是步骤:

  1. 创build一个窗口,设置AllowsTransparency =“true”,并添加一个网格
  2. 将Grid的RenderTransform设置为原点为0,1的ScaleTransform
  3. 在网格上创buildanimation,将ScaleX 0设置为1,然后稍后将不透明度从1animation到0
  4. 在构造函数中计算Window.Top和Window.Left以将窗口放置在屏幕的右下angular。

这里的所有都是它的。

使用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> 

带有后面的代码:

 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; })); } } 

由于WPF是常规的.NET库之一,答案是肯定的,可以用“常规.NET库”来完成。

如果你问是否有办法做到这一点,而不使用WPF的答案是肯定的,但它是非常复杂的,将需要更多的5天5分钟以上。

我继续为此创build一个CodePlex网站,其中包括“Toast Popups”和控制“帮助气球”。 这些版本具有比下面描述的更多的function。 https://toastspopuphelpballoon.codeplex.com

这是我寻找的解决scheme的一个很好的起点。 我做了一些修改以满足我的要求:

  • 我想停止在鼠标上的animation。
  • 鼠标离开时“重置”animation。
  • 当不透明度达到0时closures窗口。
  • 堆叠烤面包(如果窗口数量超过屏幕高度,我还没有解决问题)
  • 从我的ViewModel调用加载

这是我的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> 

后面的代码

 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(); } } } 

来自ViewModel的调用:

  private void ShowNotificationExecute() { App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action( () => { var notify = new NotificationWindow(); notify.Show(); })); } 

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> 

更新:我添加此事件处理程序窗体closures时,“放”其他窗口。

  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; } } } } 
 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(); } } 

上面的代码是精致的版本@Ray Burns的方法。 添加了时间间隔代码。 所以通知窗口会在4秒后closures

调用窗口,

 NotificationWindow nfw = new NotificationWindow(); nfw.Show(); 
 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(); 

请注意,调用线程必须是sta,因为许多ui组件需要此,而在system.timers.timerstream逝的事件下面写入以下代码

 Window1 notifyWin = new Window1(); bool? isOpen = notifyWin.ShowDialog(); if (isOpen != null && isOpen == true) { notifyWin.Close(); } System.Threading.Thread.Sleep(1000); notifyWin.ShowDialog(); 

在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; })); }